>(jsonString)
34 |
35 | _thunderOSList.value = parsedList
36 | } catch (e: Exception) {
37 | e.printStackTrace()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/ui/theme/Typography.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.unit.sp
7 |
8 | val WinlayTypography = Typography(
9 | headlineLarge = TextStyle(
10 | fontFamily = FontFamily.Default,
11 | fontSize = 30.sp
12 | ),
13 | headlineMedium = TextStyle(
14 | fontFamily = FontFamily.Default,
15 | fontSize = 24.sp
16 | ),
17 | headlineSmall = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontSize = 20.sp
20 | ),
21 | bodyLarge = TextStyle(
22 | fontFamily = FontFamily.Default,
23 | fontSize = 16.sp
24 | ),
25 | bodyMedium = TextStyle(
26 | fontFamily = FontFamily.Default,
27 | fontSize = 14.sp
28 | ),
29 | bodySmall = TextStyle(
30 | fontFamily = FontFamily.Default,
31 | fontSize = 12.sp
32 | ),
33 | labelLarge = TextStyle(
34 | fontFamily = FontFamily.Default,
35 | fontSize = 14.sp
36 | ),
37 | labelMedium = TextStyle(
38 | fontFamily = FontFamily.Default,
39 | fontSize = 12.sp
40 | ),
41 | labelSmall = TextStyle(
42 | fontFamily = FontFamily.Default,
43 | fontSize = 11.sp
44 | )
45 | )
46 |
--------------------------------------------------------------------------------
/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. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 | val Purple40 = Color(0xFF6650a4)
9 | val PurpleGrey40 = Color(0xFF625b71)
10 | val Pink40 = Color(0xFF7D5260)
11 |
12 | val WinlayPrimary = Color(0xFF1A237E)
13 | val WinlayPrimaryLight = Color(0xFF534BAE)
14 | val WinlayPrimaryDark = Color(0xFF000051)
15 | val WinlayAccent = Color(0xFFFFD700)
16 | val WinlayAccentLight = Color(0xFFFFF350)
17 | val WinlayAccentDark = Color(0xFFC8A600)
18 |
19 | val WinlaySurfaceLight = Color(0xFFFFFFFF)
20 | val WinlaySurfaceDark = Color(0xFF121212)
21 | val WinlaySurfaceVariantLight = Color(0xFFF5F5F7)
22 | val WinlaySurfaceVariantDark = Color(0xFF1E1E1E)
23 |
24 | val WinlaySuccess = Color(0xFF4CAF50)
25 | val WinlayWarning = Color(0xFFFFC107)
26 | val WinlayError = Color(0xFFF44336)
27 |
28 | val WinlayNeutral50 = Color(0xFFFAFAFA)
29 | val WinlayNeutral100 = Color(0xFFF5F5F5)
30 | val WinlayNeutral200 = Color(0xFFEEEEEE)
31 | val WinlayNeutral300 = Color(0xFFE0E0E0)
32 | val WinlayNeutral400 = Color(0xFFBDBDBD)
33 | val WinlayNeutral500 = Color(0xFF9E9E9E)
34 | val WinlayNeutral600 = Color(0xFF757575)
35 | val WinlayNeutral700 = Color(0xFF616161)
36 | val WinlayNeutral800 = Color(0xFF424242)
37 | val WinlayNeutral900 = Color(0xFF212121)
38 |
39 | val WinlayGradient = listOf(WinlayPrimary, WinlayPrimaryLight, WinlayAccent)
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/components/PremiumCard.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material3.Card
8 | import androidx.compose.material3.CardDefaults
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.clip
13 | import androidx.compose.ui.draw.shadow
14 | import androidx.compose.ui.unit.dp
15 | import com.winlay.a3x.ui.theme.LocalSpacing
16 |
17 | @Composable
18 | fun PremiumCard(
19 | modifier: Modifier = Modifier,
20 | onClick: (() -> Unit)? = null,
21 | content: @Composable () -> Unit
22 | ) {
23 | val spacing = LocalSpacing.current
24 |
25 | Card(
26 | modifier = modifier
27 | .shadow(
28 | elevation = 8.dp,
29 | shape = RoundedCornerShape(spacing.medium),
30 | ambientColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
31 | spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f)
32 | )
33 | .clip(RoundedCornerShape(spacing.medium))
34 | .clickable(enabled = onClick != null, onClick = onClick ?: {}),
35 | colors = CardDefaults.cardColors(
36 | containerColor = MaterialTheme.colorScheme.surface,
37 | contentColor = MaterialTheme.colorScheme.onSurface
38 | ),
39 | elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
40 | ) {
41 | content()
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/WelcomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import android.content.Intent
4 | import android.os.Build
5 | import android.os.Bundle
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import androidx.compose.foundation.Image
9 | import androidx.compose.foundation.layout.*
10 | import androidx.compose.material3.Surface
11 | import androidx.compose.runtime.LaunchedEffect
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.unit.dp
16 | import kotlinx.coroutines.delay
17 |
18 | class WelcomeActivity : ComponentActivity() {
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 |
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
24 | startActivity(Intent(this, MainActivity::class.java))
25 | finish()
26 | return
27 | }
28 |
29 | setContent {
30 | Surface(modifier = Modifier.fillMaxSize()) {
31 | Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
32 | Image(
33 | painter = painterResource(id = R.drawable.winlay),
34 | contentDescription = "Logo",
35 | modifier = Modifier.size(200.dp)
36 | )
37 | }
38 | }
39 |
40 | LaunchedEffect(Unit) {
41 | delay(1000)
42 | startActivity(Intent(this@WelcomeActivity, MainActivity::class.java))
43 | finish()
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/components/PremiumButton.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.components
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.ButtonDefaults
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.unit.sp
14 | import com.winlay.a3x.ui.theme.LocalSpacing
15 |
16 | @Composable
17 | fun PremiumButton(
18 | onClick: () -> Unit,
19 | modifier: Modifier = Modifier,
20 | text: String,
21 | enabled: Boolean = true
22 | ) {
23 | val spacing = LocalSpacing.current
24 |
25 | Button(
26 | onClick = onClick,
27 | modifier = modifier,
28 | enabled = enabled,
29 | shape = RoundedCornerShape(spacing.medium),
30 | colors = ButtonDefaults.buttonColors(
31 | containerColor = MaterialTheme.colorScheme.primary,
32 | contentColor = MaterialTheme.colorScheme.onPrimary,
33 | disabledContainerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
34 | disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
35 | ),
36 | contentPadding = PaddingValues(
37 | horizontal = spacing.large,
38 | vertical = spacing.medium
39 | ),
40 | elevation = ButtonDefaults.buttonElevation(
41 | defaultElevation = 4.dp,
42 | pressedElevation = 2.dp,
43 | disabledElevation = 0.dp
44 | )
45 | ) {
46 | Text(
47 | text = text,
48 | style = MaterialTheme.typography.labelLarge.copy(fontSize = 16.sp),
49 | letterSpacing = 0.5.sp
50 | )
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/components/PremiumNavigationBar.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.components
2 |
3 | import androidx.compose.foundation.layout.RowScope
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.material3.NavigationBar
6 | import androidx.compose.material3.NavigationBarItem
7 | import androidx.compose.material3.NavigationBarItemDefaults
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.winlay.a3x.ui.theme.LocalSpacing
12 |
13 | @Composable
14 | fun PremiumNavigationBar(
15 | modifier: Modifier = Modifier,
16 | content: @Composable RowScope.() -> Unit
17 | ) {
18 | val spacing = LocalSpacing.current
19 |
20 | NavigationBar(
21 | modifier = modifier.height(72.dp),
22 | containerColor = androidx.compose.material3.MaterialTheme.colorScheme.surface,
23 | contentColor = androidx.compose.material3.MaterialTheme.colorScheme.onSurface,
24 | tonalElevation = 8.dp,
25 | content = content
26 | )
27 | }
28 |
29 | @Composable
30 | fun RowScope.PremiumNavigationBarItem(
31 | selected: Boolean,
32 | onClick: () -> Unit,
33 | icon: @Composable () -> Unit,
34 | label: @Composable () -> Unit,
35 | modifier: Modifier = Modifier
36 | ) {
37 | NavigationBarItem(
38 | selected = selected,
39 | onClick = onClick,
40 | icon = icon,
41 | label = label,
42 | modifier = modifier,
43 | colors = NavigationBarItemDefaults.colors(
44 | selectedIconColor = androidx.compose.material3.MaterialTheme.colorScheme.primary,
45 | selectedTextColor = androidx.compose.material3.MaterialTheme.colorScheme.primary,
46 | unselectedIconColor = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant,
47 | unselectedTextColor = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant,
48 | indicatorColor = androidx.compose.material3.MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
49 | )
50 | )
51 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_instagram.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.10.0"
3 | kotlin = "2.0.21"
4 | coreKtx = "1.10.1"
5 | junit = "4.13.2"
6 | junitVersion = "1.1.5"
7 | espressoCore = "3.5.1"
8 | lifecycleRuntimeKtx = "2.6.1"
9 | activityCompose = "1.8.0"
10 | composeBom = "2024.09.00"
11 | material3 = "1.3.2"
12 | navigationCompose = "2.9.0"
13 | navigationRuntimeAndroid = "2.9.0"
14 | foundationLayoutAndroid = "1.7.2"
15 |
16 | [libraries]
17 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
18 | androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
19 | junit = { group = "junit", name = "junit", version.ref = "junit" }
20 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
21 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
22 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
23 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
24 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
25 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
26 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
27 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
28 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
29 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
30 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
31 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
32 | androidx-navigation-runtime-android = { group = "androidx.navigation", name = "navigation-runtime-android", version.ref = "navigationRuntimeAndroid" }
33 | androidx-foundation-layout-android = { group = "androidx.compose.foundation", name = "foundation-layout-android", version.ref = "foundationLayoutAndroid" }
34 | material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
35 |
36 | [plugins]
37 | android-application = { id = "com.android.application", version.ref = "agp" }
38 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
39 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
40 |
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚠️ Discontinued Project
2 |
3 | **This repository is discontinued and no longer maintained.**
4 | The source code is still available for **archival and reference purposes**, and you are free to **fork the project**.
5 |
6 | ---
7 |
8 |
9 |
10 |
11 |
12 | Winlay
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | **Winlay** was a free and open-source app developed by **A3X**.
39 | It allowed you to download Windows, Android, and Linux files that could be used to test emulators such as **Limbo PC Emulator x86**, **VirtualBox**, **QEMU** and more.
40 |
41 | ### License
42 |
43 | Winlay was licensed under the **GNU General Public License v3.0 (GPLv3)**.
44 | The source code is still available in the [`/sourcecode`](https://github.com/a3x-xyz/Winlay/tree/main/sourcecode) folder of this repository.
45 |
46 | ### Screenshot (archived)
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/json/android.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Android 3.2",
4 | "description": "No description",
5 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231108-193238_orig.jpg",
6 | "downloads": [
7 | {
8 | "label": "Download (Mediafire)",
9 | "url": "https://www.mediafire.com/file/cq4dwdvs9zen03z/Android+3.2.rar/file"
10 | }
11 | ]
12 | },
13 | {
14 | "name": "Android TV x86",
15 | "description": "No description",
16 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231026-083841_orig.jpg",
17 | "downloads": [
18 | {
19 | "label": "Download (Mediafire)",
20 | "url": "https://www.mediafire.com/file/9q65bf5r22p7rep/Android_TV_x86.rar/file"
21 | }
22 | ]
23 | },
24 | {
25 | "name": "Android 9.0",
26 | "description": "No description",
27 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230908-190913_orig.jpg",
28 | "downloads": [
29 | {
30 | "label": "Download (Pixeldrain)",
31 | "url": "https://pixeldrain.com/u/uuMhJGzg"
32 | }
33 | ]
34 | },
35 | {
36 | "name": "Android 6.0",
37 | "description": "No description",
38 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230617-112223_orig.jpg",
39 | "downloads": [
40 | {
41 | "label": "Download (Mediafire)",
42 | "url": "https://www.mediafire.com/file/hj2z1qb1g6wbgsz/android-x86-6.0-r1.rar/file"
43 | }
44 | ]
45 | },
46 | {
47 | "name": "Android 5.1",
48 | "description": "No description",
49 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230617-112157_orig.jpg",
50 | "downloads": [
51 | {
52 | "label": "Download (Mediafire)",
53 | "url": "https://www.mediafire.com/file/beo46gnzv72ykcd/Android_5.1-rc1.rar/file"
54 | }
55 | ]
56 | },
57 | {
58 | "name": "Android 4.4",
59 | "description": "No description",
60 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230604-184054_orig.jpg",
61 | "downloads": [
62 | {
63 | "label": "Download (Mediafire)",
64 | "url": "https://www.mediafire.com/file/g5jhq1cmjoj2iqc/Android_4.4_RC1.rar/file"
65 | }
66 | ]
67 | },
68 | {
69 | "name": "Android 2.2",
70 | "description": "No description",
71 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230604-184026_orig.jpg",
72 | "downloads": [
73 | {
74 | "label": "Download (Mediafire)",
75 | "url": "https://www.mediafire.com/file/a0m5dgzs1ywxpcs/Android_2.2.rar/file"
76 | }
77 | ]
78 | },
79 | {
80 | "name": "Android 1.6",
81 | "description": "No description",
82 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230601-094904_orig.jpg",
83 | "downloads": [
84 | {
85 | "label": "Download (Mediafire)",
86 | "url": "https://www.mediafire.com/file/4ar5yshyx7b9788/Android_1.6.rar/file"
87 | }
88 | ]
89 | }
90 | ]
91 |
--------------------------------------------------------------------------------
/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/com/winlay/a3x/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.foundation.isSystemInDarkTheme
9 | import androidx.compose.material3.*
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.compose.ui.platform.LocalView
14 | import androidx.core.view.WindowCompat
15 | import androidx.lifecycle.viewmodel.compose.viewModel
16 | import com.winlay.a3x.ui.theme.WinlayTheme
17 |
18 | class MainActivity : ComponentActivity() {
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | enableEdgeToEdge()
22 |
23 | setContent {
24 | val viewModel: ThemeViewModel = viewModel()
25 | val theme by viewModel.theme.collectAsState()
26 |
27 | val isDarkTheme = when (theme) {
28 | ThemeOption.SYSTEM -> isSystemInDarkTheme()
29 | ThemeOption.DARK -> true
30 | ThemeOption.LIGHT -> false
31 | }
32 |
33 | val view = LocalView.current
34 | val context = LocalContext.current
35 | var isConnected by remember { mutableStateOf(true) }
36 |
37 | LaunchedEffect(Unit) {
38 | isConnected = isInternetAvailable(context)
39 | }
40 |
41 | SideEffect {
42 | val window = this@MainActivity.window
43 | WindowCompat.setDecorFitsSystemWindows(window, false)
44 | WindowCompat.getInsetsController(window, view)
45 | .isAppearanceLightStatusBars = !isDarkTheme
46 | window.statusBarColor = Color.Transparent.value.toInt()
47 | }
48 |
49 | WinlayTheme(darkTheme = isDarkTheme) {
50 | MainScreen()
51 | if (!isConnected) {
52 | AlertDialog(
53 | onDismissRequest = { /* Prevent dismiss on tap outside */ },
54 | confirmButton = {
55 | TextButton(onClick = {
56 | isConnected = isInternetAvailable(context)
57 | }) {
58 | Text("Retry")
59 | }
60 | },
61 | dismissButton = {
62 | TextButton(onClick = {
63 | (context as? Activity)?.finish()
64 | }) {
65 | Text("Exit")
66 | }
67 | },
68 | title = {
69 | Text("Internet Required")
70 | },
71 | text = {
72 | Text("Please connect to the internet to continue using the app.")
73 | }
74 | )
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/json/apps.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "limbo-pc-emulator-x86",
4 | "name": "Limbo PC Emulator x86",
5 | "description": "Limbo is a QEMU-based emulator for Android supports emulation for these architectures: x86/x86_64 ARM/ARM64 PowerPC/PowerPC64 Sparc.",
6 | "logoUrl": "https://winlayassets.a3x.xyz/images/limbo-pc-emulator.png",
7 | "screenshots": [
8 | "https://winlayassets.a3x.xyz/images/640.png"
9 | ],
10 | "downloadUrl": "https://github.com/limboemu/limbo/releases/download/v6.0.1-LimboEmulator/limbo-android-x86-6.0.1-qemu-5.1.0.apk"
11 | },
12 | {
13 | "id": "termux",
14 | "name": "Termux",
15 | "description": "Termux combines powerful terminal emulation with an extensive Linux package collection.\n\n* Enjoy the bash and zsh shells.\n* Edit files with nano and vim.\n* Access servers over ssh.\n* Compile code with gcc and clang.\n* Use the python console as a pocket calculator.\n* Check out projects with git and subversion.\n* Run text-based games with frotz.\n\nAt first start a small base system is downloaded - desired packages can then be installed using the apt package manager known from the Debian and Ubuntu Linux distributions. Access the built-in help by long-pressing anywhere on the terminal and selecting the Help menu option to learn more.\n\nRead help online: https://wiki.termux.com/\n\nReddit Community: https://termux.com/community",
16 | "logoUrl": "https://winlayassets.a3x.xyz/images/termux.png",
17 | "screenshots": [
18 | "https://f-droid.org/repo/com.termux/en-US/phoneScreenshots/1.jpg",
19 | "https://f-droid.org/repo/com.termux/en-US/phoneScreenshots/2.jpg",
20 | "https://f-droid.org/repo/com.termux/en-US/phoneScreenshots/3.jpg",
21 | "https://f-droid.org/repo/com.termux/en-US/phoneScreenshots/4.jpg"
22 | ],
23 | "downloadUrl": "https://f-droid.org/repo/com.termux_1022.apk"
24 | },
25 | {
26 | "id": "vectras",
27 | "name": "Vectras",
28 | "description": "No description",
29 | "logoUrl": "https://raw.githubusercontent.com/xoureldeen/Vectras-VM-Android/refs/heads/master/resources/vectrasvm.png",
30 | "screenshots": [
31 | "https://www.openapk.net/images/screenshots/vectras-vm-apk.jpeg"
32 | ],
33 | "downloadUrl": "https://github.com/xoureldeen/Vectras-VM-Android/releases/download/v2.9.5/app-release-universal.apk"
34 | },
35 | {
36 | "id": "winlator",
37 | "name": "Winlator",
38 | "description": "Winlator is an Android application that lets you to run Windows (x86_64) applications with Wine and Box86/Box64.",
39 | "logoUrl": "https://winlayassets.a3x.xyz/images/winlator.png",
40 | "screenshots": [
41 | "https://www.openapk.net/images/screenshots/winlator-1.jpg"
42 | ],
43 | "downloadUrl": "https://github.com/brunodev85/winlator/releases/download/v10.1.0/Winlator_10.1.apk"
44 | },
45 | {
46 | "id": "limbozelimod",
47 | "name": "Limbo Zeli Mod",
48 | "description": "Limbo PC x86 Emulator Modified by Zeli.",
49 | "logoUrl": "https://winlayassets.a3x.xyz/images/windows_11_my_computer.png",
50 | "screenshots": [
51 | "https://dn721709.ca.archive.org/0/items/base_20250404/Screenshot_20250404-090207.png",
52 | "https://ia600709.us.archive.org/7/items/base_20250404/Screenshot_20250404-090257.png"
53 | ],
54 | "downloadUrl": "https://drive.google.com/file/d/1nmMbsyYrNm_i_Jpr6klV9aGAOpMWmYD2/view?usp=drivesdk"
55 | }
56 | ]
57 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | kotlin("plugin.serialization") version "1.9.10"
6 | }
7 |
8 | android {
9 | namespace = "com.winlay.a3x"
10 | compileSdk = 35
11 |
12 | defaultConfig {
13 | applicationId = "com.winlay.a3x"
14 | minSdk = 26
15 | targetSdk = 35
16 | versionCode = 1
17 | versionName = "1.7"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = false
25 | proguardFiles(
26 | getDefaultProguardFile("proguard-android-optimize.txt"),
27 | "proguard-rules.pro"
28 | )
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility = JavaVersion.VERSION_11
33 | targetCompatibility = JavaVersion.VERSION_11
34 | }
35 | kotlinOptions {
36 | jvmTarget = "11"
37 | }
38 | buildFeatures {
39 | compose = true
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
45 | implementation("io.ktor:ktor-client-content-negotiation:2.3.4")
46 | implementation("io.ktor:ktor-client-json:2.3.4")
47 | implementation("io.ktor:ktor-client-okhttp:2.3.4")
48 | implementation("io.ktor:ktor-client-core:2.3.4")
49 | implementation("com.kizitonwose.calendar:compose:2.3.0")
50 | implementation("androidx.compose.foundation:foundation:1.6.0")
51 | implementation("com.google.accompanist:accompanist-swiperefresh:0.33.2-alpha")
52 | implementation("androidx.browser:browser:1.8.0")
53 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
54 | implementation("androidx.compose.material3:material3:1.2.0")
55 | implementation("com.squareup.okhttp3:okhttp:4.12.0")
56 | implementation("androidx.datastore:datastore-preferences:1.1.1")
57 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
58 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
59 | implementation("androidx.compose.material:material-icons-extended:1.6.1")
60 | implementation("androidx.navigation:navigation-compose:2.7.7")
61 | implementation("io.ktor:ktor-client-core:2.3.8")
62 | implementation("io.ktor:ktor-client-cio:2.3.8")
63 | implementation("io.ktor:ktor-client-serialization:2.3.8")
64 | implementation("io.ktor:ktor-client-content-negotiation:2.3.8")
65 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
66 | implementation("io.coil-kt:coil-compose:2.5.0")
67 | implementation("androidx.compose.ui:ui:1.5.0")
68 | implementation(libs.androidx.navigation.compose)
69 | implementation(libs.material3)
70 | implementation(libs.androidx.core.ktx)
71 | implementation(libs.androidx.lifecycle.runtime.ktx)
72 | implementation(libs.androidx.activity.compose)
73 | implementation(platform(libs.androidx.compose.bom))
74 | implementation(libs.androidx.ui)
75 | implementation(libs.androidx.ui.graphics)
76 | implementation(libs.androidx.ui.tooling.preview)
77 | implementation(libs.androidx.material3)
78 | implementation(libs.androidx.navigation.runtime.android)
79 | implementation(libs.androidx.foundation.layout.android)
80 | testImplementation(libs.junit)
81 | androidTestImplementation(libs.androidx.junit)
82 | androidTestImplementation(libs.androidx.espresso.core)
83 | androidTestImplementation(platform(libs.androidx.compose.bom))
84 | androidTestImplementation(libs.androidx.ui.test.junit4)
85 | debugImplementation(libs.androidx.ui.tooling)
86 | debugImplementation(libs.androidx.ui.test.manifest)
87 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | val PremiumTypography = Typography(
10 | displayLarge = TextStyle(
11 | fontFamily = FontFamily.Default,
12 | fontWeight = FontWeight.Light,
13 | fontSize = 57.sp,
14 | lineHeight = 64.sp,
15 | letterSpacing = (-0.25).sp
16 | ),
17 | displayMedium = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.Normal,
20 | fontSize = 45.sp,
21 | lineHeight = 52.sp,
22 | letterSpacing = 0.sp
23 | ),
24 | displaySmall = TextStyle(
25 | fontFamily = FontFamily.Default,
26 | fontWeight = FontWeight.Normal,
27 | fontSize = 36.sp,
28 | lineHeight = 44.sp,
29 | letterSpacing = 0.sp
30 | ),
31 | headlineLarge = TextStyle(
32 | fontFamily = FontFamily.Default,
33 | fontWeight = FontWeight.SemiBold,
34 | fontSize = 32.sp,
35 | lineHeight = 40.sp,
36 | letterSpacing = 0.sp
37 | ),
38 | headlineMedium = TextStyle(
39 | fontFamily = FontFamily.Default,
40 | fontWeight = FontWeight.SemiBold,
41 | fontSize = 28.sp,
42 | lineHeight = 36.sp,
43 | letterSpacing = 0.sp
44 | ),
45 | headlineSmall = TextStyle(
46 | fontFamily = FontFamily.Default,
47 | fontWeight = FontWeight.SemiBold,
48 | fontSize = 24.sp,
49 | lineHeight = 32.sp,
50 | letterSpacing = 0.sp
51 | ),
52 | titleLarge = TextStyle(
53 | fontFamily = FontFamily.Default,
54 | fontWeight = FontWeight.SemiBold,
55 | fontSize = 22.sp,
56 | lineHeight = 28.sp,
57 | letterSpacing = 0.sp
58 | ),
59 | titleMedium = TextStyle(
60 | fontFamily = FontFamily.Default,
61 | fontWeight = FontWeight.SemiBold,
62 | fontSize = 18.sp,
63 | lineHeight = 24.sp,
64 | letterSpacing = 0.15.sp
65 | ),
66 | titleSmall = TextStyle(
67 | fontFamily = FontFamily.Default,
68 | fontWeight = FontWeight.Medium,
69 | fontSize = 14.sp,
70 | lineHeight = 20.sp,
71 | letterSpacing = 0.1.sp
72 | ),
73 | bodyLarge = TextStyle(
74 | fontFamily = FontFamily.Default,
75 | fontWeight = FontWeight.Normal,
76 | fontSize = 16.sp,
77 | lineHeight = 24.sp,
78 | letterSpacing = 0.5.sp
79 | ),
80 | bodyMedium = TextStyle(
81 | fontFamily = FontFamily.Default,
82 | fontWeight = FontWeight.Normal,
83 | fontSize = 14.sp,
84 | lineHeight = 20.sp,
85 | letterSpacing = 0.25.sp
86 | ),
87 | bodySmall = TextStyle(
88 | fontFamily = FontFamily.Default,
89 | fontWeight = FontWeight.Normal,
90 | fontSize = 12.sp,
91 | lineHeight = 16.sp,
92 | letterSpacing = 0.4.sp
93 | ),
94 | labelLarge = TextStyle(
95 | fontFamily = FontFamily.Default,
96 | fontWeight = FontWeight.Medium,
97 | fontSize = 14.sp,
98 | lineHeight = 20.sp,
99 | letterSpacing = 0.1.sp
100 | ),
101 | labelMedium = TextStyle(
102 | fontFamily = FontFamily.Default,
103 | fontWeight = FontWeight.Medium,
104 | fontSize = 12.sp,
105 | lineHeight = 16.sp,
106 | letterSpacing = 0.5.sp
107 | ),
108 | labelSmall = TextStyle(
109 | fontFamily = FontFamily.Default,
110 | fontWeight = FontWeight.Medium,
111 | fontSize = 10.sp,
112 | lineHeight = 16.sp,
113 | letterSpacing = 0.sp
114 | )
115 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/MainScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Home
7 | import androidx.compose.material.icons.filled.Settings
8 | import androidx.compose.material.icons.filled.Store
9 | import androidx.compose.material3.*
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.vector.ImageVector
14 | import androidx.navigation.NavGraph.Companion.findStartDestination
15 | import androidx.navigation.NavHostController
16 | import androidx.navigation.compose.*
17 | import com.winlay.a3x.ui.screens.ThunderOSScreen
18 | import androidx.compose.material3.NavigationBar
19 | import androidx.compose.material3.NavigationBarItem
20 | import androidx.compose.material3.NavigationBarItemDefaults
21 | import com.winlay.a3x.components.PremiumNavigationBar
22 |
23 | @Composable
24 | fun MainScreen() {
25 | val navController = rememberNavController()
26 |
27 | val navBackStackEntry by navController.currentBackStackEntryAsState()
28 | val currentRoute = navBackStackEntry?.destination?.route
29 |
30 | val showBottomBar = currentRoute == "home" || currentRoute == "settings" || currentRoute == "appstore"
31 |
32 | Scaffold(
33 | bottomBar = {
34 | if (showBottomBar) {
35 | BottomNavigationBar(navController)
36 | }
37 | },
38 | modifier = Modifier.fillMaxSize()
39 | ) { innerPadding ->
40 | NavHost(
41 | navController = navController,
42 | startDestination = "home",
43 | modifier = Modifier.padding(innerPadding)
44 | ) {
45 | composable("home") { HomeScreen(navController) }
46 | composable("settings") { SettingsScreen() }
47 | composable("appstore") { AppStoreScreen(navController) }
48 | composable("appdetail/{appId}") { backStackEntry ->
49 | val appId = backStackEntry.arguments?.getString("appId") ?: ""
50 | AppDetailScreen(appId, navController)
51 | }
52 | composable("detail/{name}") { backStackEntry ->
53 | val name = backStackEntry.arguments?.getString("name") ?: ""
54 | DetailScreen(name, navController)
55 | }
56 | composable("event") { EventScreen(navController) }
57 | composable("thunderos") { ThunderOSScreen(navController) }
58 | }
59 | }
60 | }
61 |
62 | @Composable
63 | fun BottomNavigationBar(navController: NavHostController) {
64 | val items = listOf(
65 | BottomNavItem("Home", "home", Icons.Default.Home),
66 | BottomNavItem("App Store", "appstore", Icons.Default.Store),
67 | BottomNavItem("Settings", "settings", Icons.Default.Settings)
68 | )
69 | NavigationBar {
70 | val navBackStackEntry by navController.currentBackStackEntryAsState()
71 | val currentDestination = navBackStackEntry?.destination
72 |
73 | items.forEach { item ->
74 | NavigationBarItem(
75 | selected = currentDestination?.route == item.route,
76 | onClick = {
77 | navController.navigate(item.route) {
78 | popUpTo(navController.graph.findStartDestination().id) {
79 | saveState = true
80 | }
81 | launchSingleTop = true
82 | restoreState = true
83 | }
84 | },
85 | icon = { Icon(item.icon, contentDescription = item.label) },
86 | label = { Text(item.label) }
87 | )
88 | }
89 | }
90 | }
91 |
92 | data class BottomNavItem(
93 | val label: String,
94 | val route: String,
95 | val icon: ImageVector
96 | )
97 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.ui.theme
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.runtime.compositionLocalOf
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.unit.dp
16 |
17 | private val DarkColorScheme = darkColorScheme(
18 | primary = WinlayPrimaryLight,
19 | onPrimary = Color.White,
20 | primaryContainer = WinlayPrimaryDark,
21 | onPrimaryContainer = WinlayAccentLight,
22 |
23 | secondary = WinlayAccent,
24 | onSecondary = WinlayNeutral900,
25 | secondaryContainer = WinlayAccentDark,
26 | onSecondaryContainer = WinlayNeutral900,
27 |
28 | tertiary = WinlayNeutral300,
29 | onTertiary = WinlayNeutral900,
30 | tertiaryContainer = WinlayNeutral800,
31 | onTertiaryContainer = WinlayNeutral100,
32 |
33 | background = WinlaySurfaceDark,
34 | onBackground = WinlayNeutral100,
35 |
36 | surface = WinlaySurfaceVariantDark,
37 | onSurface = WinlayNeutral100,
38 | surfaceVariant = WinlayNeutral800,
39 | onSurfaceVariant = WinlayNeutral200,
40 |
41 | error = WinlayError,
42 | onError = Color.White,
43 | errorContainer = Color(0xFF690005),
44 | onErrorContainer = Color(0xFFFFDAD6),
45 |
46 | outline = WinlayNeutral600,
47 | outlineVariant = WinlayNeutral700
48 | )
49 |
50 | private val LightColorScheme = lightColorScheme(
51 | primary = WinlayPrimary,
52 | onPrimary = Color.White,
53 | primaryContainer = WinlayPrimaryLight,
54 | onPrimaryContainer = WinlayPrimaryDark,
55 |
56 | secondary = WinlayAccent,
57 | onSecondary = WinlayNeutral900,
58 | secondaryContainer = WinlayAccentLight,
59 | onSecondaryContainer = WinlayAccentDark,
60 |
61 | tertiary = WinlayNeutral700,
62 | onTertiary = Color.White,
63 | tertiaryContainer = WinlayNeutral300,
64 | onTertiaryContainer = WinlayNeutral800,
65 |
66 | background = WinlaySurfaceLight,
67 | onBackground = WinlayNeutral900,
68 |
69 | surface = WinlaySurfaceVariantLight,
70 | onSurface = WinlayNeutral900,
71 | surfaceVariant = WinlayNeutral200,
72 | onSurfaceVariant = WinlayNeutral800,
73 |
74 | error = WinlayError,
75 | onError = Color.White,
76 | errorContainer = Color(0xFFFFDAD6),
77 | onErrorContainer = Color(0xFFFFDAD6),
78 |
79 | outline = WinlayNeutral400,
80 | outlineVariant = WinlayNeutral300
81 | )
82 |
83 | @Composable
84 | fun WinlayTheme(
85 | darkTheme: Boolean = isSystemInDarkTheme(),
86 | dynamicColor: Boolean = true,
87 | content: @Composable () -> Unit
88 | ) {
89 | val colorScheme = when {
90 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
91 | val context = LocalContext.current
92 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
93 | }
94 | darkTheme -> DarkColorScheme
95 | else -> LightColorScheme
96 | }
97 |
98 | CompositionLocalProvider(
99 | LocalSpacing provides Dimensions()
100 | ) {
101 | MaterialTheme(
102 | colorScheme = colorScheme,
103 | typography = WinlayTypography,
104 | content = content
105 | )
106 | }
107 | }
108 |
109 | data class Dimensions(
110 | val tiny: androidx.compose.ui.unit.Dp = 4.dp,
111 | val small: androidx.compose.ui.unit.Dp = 8.dp,
112 | val medium: androidx.compose.ui.unit.Dp = 16.dp,
113 | val large: androidx.compose.ui.unit.Dp = 24.dp,
114 | val xlarge: androidx.compose.ui.unit.Dp = 32.dp,
115 | val xxlarge: androidx.compose.ui.unit.Dp = 48.dp
116 | )
117 |
118 | val LocalSpacing = compositionLocalOf { Dimensions() }
119 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/ThunderOSScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x.ui.screens
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.ArrowBack
9 | import androidx.compose.material3.*
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.style.TextAlign
14 | import androidx.compose.ui.unit.dp
15 | import androidx.navigation.NavController
16 | import coil.compose.rememberAsyncImagePainter
17 | import io.ktor.client.HttpClient
18 | import io.ktor.client.engine.cio.CIO
19 | import io.ktor.client.request.get
20 | import io.ktor.client.statement.bodyAsText
21 | import kotlinx.serialization.Serializable
22 | import kotlinx.serialization.json.Json
23 | import androidx.compose.ui.platform.LocalUriHandler
24 |
25 | @Serializable
26 | data class ThunderOSItem(
27 | val name: String,
28 | val image: String,
29 | val download: String
30 | )
31 |
32 | @OptIn(ExperimentalMaterial3Api::class)
33 | @Composable
34 | fun ThunderOSScreen(navController: NavController) {
35 | var items by remember { mutableStateOf>(emptyList()) }
36 | var isLoading by remember { mutableStateOf(true) }
37 |
38 | val client = remember { HttpClient(CIO) }
39 | DisposableEffect(Unit) {
40 | onDispose { client.close() }
41 | }
42 |
43 | LaunchedEffect(Unit) {
44 | try {
45 | val jsonText = client
46 | .get("https://winlayassets.a3x.xyz/json/thunderos.json")
47 | .bodyAsText()
48 |
49 | val parsed = Json { ignoreUnknownKeys = true }
50 | .decodeFromString>(jsonText)
51 |
52 | items = parsed
53 | } catch (e: Exception) {
54 | e.printStackTrace()
55 | items = emptyList()
56 | } finally {
57 | isLoading = false
58 | }
59 | }
60 |
61 | val uriHandler = LocalUriHandler.current
62 |
63 | Scaffold(
64 | topBar = {
65 | TopAppBar(
66 | title = { Text("Thunder OS") },
67 | navigationIcon = {
68 | IconButton(onClick = { navController.popBackStack() }) {
69 | Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
70 | }
71 | }
72 | )
73 | }
74 | ) { padding ->
75 | Box(
76 | modifier = Modifier
77 | .fillMaxSize()
78 | .padding(padding)
79 | ) {
80 | when {
81 | isLoading -> {
82 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
83 | }
84 |
85 | items.isEmpty() -> {
86 | Text(
87 | "No Thunder OS items found",
88 | modifier = Modifier.align(Alignment.Center),
89 | style = MaterialTheme.typography.bodyMedium
90 | )
91 | }
92 |
93 | else -> {
94 | LazyColumn(
95 | modifier = Modifier.fillMaxSize(),
96 | contentPadding = PaddingValues(16.dp),
97 | verticalArrangement = Arrangement.spacedBy(20.dp)
98 | ) {
99 | items(items) { item ->
100 | Column(
101 | modifier = Modifier.fillMaxWidth(),
102 | verticalArrangement = Arrangement.spacedBy(12.dp),
103 | horizontalAlignment = Alignment.CenterHorizontally
104 | ) {
105 | Image(
106 | painter = rememberAsyncImagePainter(item.image),
107 | contentDescription = item.name,
108 | modifier = Modifier
109 | .fillMaxWidth()
110 | .height(200.dp)
111 | )
112 |
113 | Row(
114 | modifier = Modifier.fillMaxWidth(),
115 | horizontalArrangement = Arrangement.SpaceBetween,
116 | verticalAlignment = Alignment.CenterVertically
117 | ) {
118 | Text(
119 | text = item.name,
120 | style = MaterialTheme.typography.titleMedium,
121 | modifier = Modifier.weight(1f),
122 | textAlign = TextAlign.Start
123 | )
124 | Button(onClick = { uriHandler.openUri(item.download) }) {
125 | Text("Download")
126 | }
127 | }
128 | }
129 | }
130 | }
131 | }
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/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/java/com/winlay/a3x/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.filled.Android
8 | import androidx.compose.material.icons.filled.ArrowBack
9 | import androidx.compose.material.icons.filled.ArrowForward
10 | import androidx.compose.material.icons.filled.Computer
11 | import androidx.compose.material.icons.filled.Event
12 | import androidx.compose.material.icons.filled.Window
13 | import androidx.compose.material3.*
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.ColorFilter
18 | import androidx.compose.ui.graphics.vector.ImageVector
19 | import androidx.compose.ui.platform.LocalUriHandler
20 | import androidx.compose.ui.res.painterResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.navigation.NavController
23 | import com.winlay.a3x.components.PremiumCard
24 | import com.winlay.a3x.ui.theme.LocalSpacing
25 |
26 | @Composable
27 | fun HomeScreen(navController: NavController) {
28 | val uriHandler = LocalUriHandler.current
29 | val spacing = LocalSpacing.current
30 |
31 | Column(
32 | modifier = Modifier
33 | .fillMaxSize()
34 | .padding(spacing.medium)
35 | ) {
36 | Text(
37 | "Home",
38 | style = MaterialTheme.typography.headlineLarge,
39 | modifier = Modifier.padding(bottom = spacing.large)
40 | )
41 |
42 | PremiumCard(
43 | modifier = Modifier
44 | .fillMaxWidth()
45 | .padding(bottom = spacing.large)
46 | ) {
47 | Column(
48 | modifier = Modifier
49 | .fillMaxWidth()
50 | .padding(spacing.medium)
51 | ) {
52 | Text(
53 | "Download",
54 | style = MaterialTheme.typography.titleLarge,
55 | color = MaterialTheme.colorScheme.primary,
56 | modifier = Modifier.padding(bottom = spacing.medium)
57 | )
58 |
59 | DownloadItem(icon = Icons.Filled.Computer, label = "Linux") {
60 | navController.navigate("detail/Linux")
61 | }
62 | DownloadItem(icon = Icons.Filled.Android, label = "Android") {
63 | navController.navigate("detail/Android")
64 | }
65 | DownloadItem(icon = Icons.Filled.Window, label = "Windows") {
66 | navController.navigate("detail/Windows")
67 | }
68 | }
69 | }
70 |
71 | PremiumCard(
72 | modifier = Modifier
73 | .fillMaxWidth()
74 | .padding(bottom = spacing.large)
75 | ) {
76 | Column(
77 | modifier = Modifier
78 | .fillMaxWidth()
79 | .padding(spacing.medium)
80 | ) {
81 | Text(
82 | "Connect With Us",
83 | style = MaterialTheme.typography.titleLarge,
84 | color = MaterialTheme.colorScheme.primary,
85 | modifier = Modifier.padding(bottom = spacing.medium)
86 | )
87 |
88 | SocialItem(resourceId = R.drawable.ic_discord, label = "Discord") {
89 | uriHandler.openUri("https://discord.gg/EBvgf8hpvw")
90 | }
91 | SocialItem(resourceId = R.drawable.ic_youtube, label = "YouTube") {
92 | uriHandler.openUri("https://youtube.com/@rimvydoplus")
93 | }
94 | SocialItem(resourceId = R.drawable.ic_instagram, label = "Instagram") {
95 | uriHandler.openUri("https://www.instagram.com/rimvydop")
96 | }
97 | SocialItem(resourceId = R.drawable.ic_x, label = "X") {
98 | uriHandler.openUri("https://x.com/rimvydopmusic")
99 | }
100 | }
101 | }
102 |
103 | PremiumCard(
104 | modifier = Modifier.fillMaxWidth()
105 | ) {
106 | Column(
107 | modifier = Modifier
108 | .fillMaxWidth()
109 | .padding(spacing.medium)
110 | ) {
111 | Text(
112 | "Explore More",
113 | style = MaterialTheme.typography.titleLarge,
114 | color = MaterialTheme.colorScheme.primary,
115 | modifier = Modifier.padding(bottom = spacing.medium)
116 | )
117 |
118 | OtherItem(icon = Icons.Filled.Event, label = "Events") {
119 | navController.navigate("event")
120 | }
121 | OtherItem(icon = Icons.Filled.Computer, label = "Thunder OS") {
122 | navController.navigate("thunderos")
123 | }
124 | }
125 | }
126 | }
127 | }
128 |
129 | @Composable
130 | fun DownloadItem(icon: ImageVector, label: String, onClick: () -> Unit) {
131 | val spacing = LocalSpacing.current
132 |
133 | Row(
134 | verticalAlignment = Alignment.CenterVertically,
135 | modifier = Modifier
136 | .fillMaxWidth()
137 | .padding(vertical = spacing.small)
138 | .clickable { onClick() }
139 | ) {
140 | Icon(
141 | imageVector = icon,
142 | contentDescription = label,
143 | tint = MaterialTheme.colorScheme.primary
144 | )
145 | Spacer(modifier = Modifier.width(spacing.medium))
146 | Text(
147 | text = label,
148 | style = MaterialTheme.typography.bodyLarge,
149 | color = MaterialTheme.colorScheme.onSurface
150 | )
151 | Spacer(modifier = Modifier.weight(1f))
152 | Icon(
153 | imageVector = Icons.Filled.ArrowForward,
154 | contentDescription = "Go",
155 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)
156 | )
157 | }
158 | }
159 |
160 | @Composable
161 | fun SocialItem(resourceId: Int, label: String, onClick: () -> Unit) {
162 | val spacing = LocalSpacing.current
163 |
164 | Row(
165 | verticalAlignment = Alignment.CenterVertically,
166 | modifier = Modifier
167 | .fillMaxWidth()
168 | .padding(vertical = spacing.small)
169 | .clickable { onClick() }
170 | ) {
171 | Image(
172 | painter = painterResource(id = resourceId),
173 | contentDescription = label,
174 | modifier = Modifier.size(24.dp),
175 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
176 | )
177 | Spacer(modifier = Modifier.width(spacing.medium))
178 | Text(
179 | text = label,
180 | style = MaterialTheme.typography.bodyLarge,
181 | color = MaterialTheme.colorScheme.onSurface
182 | )
183 | Spacer(modifier = Modifier.weight(1f))
184 | Icon(
185 | imageVector = Icons.Filled.ArrowForward,
186 | contentDescription = "Go",
187 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)
188 | )
189 | }
190 | }
191 |
192 | @Composable
193 | fun OtherItem(icon: ImageVector, label: String, onClick: () -> Unit) {
194 | val spacing = LocalSpacing.current
195 |
196 | Row(
197 | verticalAlignment = Alignment.CenterVertically,
198 | modifier = Modifier
199 | .fillMaxWidth()
200 | .padding(vertical = spacing.small)
201 | .clickable { onClick() }
202 | ) {
203 | Icon(
204 | imageVector = icon,
205 | contentDescription = label,
206 | tint = MaterialTheme.colorScheme.primary
207 | )
208 | Spacer(modifier = Modifier.width(spacing.medium))
209 | Text(
210 | text = label,
211 | style = MaterialTheme.typography.bodyLarge,
212 | color = MaterialTheme.colorScheme.onSurface
213 | )
214 | Spacer(modifier = Modifier.weight(1f))
215 | Icon(
216 | imageVector = Icons.Filled.ArrowForward,
217 | contentDescription = "Go",
218 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f)
219 | )
220 | }
221 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/AppStoreScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.filled.ArrowForward
11 | import androidx.compose.material.icons.filled.Search
12 | import androidx.compose.material3.*
13 | import androidx.compose.runtime.*
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.unit.dp
19 | import androidx.navigation.NavController
20 | import coil.compose.rememberAsyncImagePainter
21 | import com.google.accompanist.swiperefresh.SwipeRefresh
22 | import com.google.accompanist.swiperefresh.SwipeRefreshState
23 | import kotlinx.coroutines.launch
24 | import com.winlay.a3x.components.PremiumCard
25 | import com.winlay.a3x.ui.theme.LocalSpacing
26 |
27 | @OptIn(ExperimentalMaterial3Api::class)
28 | @Composable
29 | fun AppStoreScreen(navController: NavController) {
30 | val scope = rememberCoroutineScope()
31 | var apps by remember { mutableStateOf>(emptyList()) }
32 | var isLoading by remember { mutableStateOf(true) }
33 | var isRefreshing by remember { mutableStateOf(false) }
34 | var showSearch by remember { mutableStateOf(false) }
35 | var searchQuery by remember { mutableStateOf("") }
36 | val spacing = LocalSpacing.current
37 |
38 | fun loadApps() {
39 | scope.launch {
40 | isLoading = true
41 | try {
42 | apps = AppRepository.loadAppsFromGitHub()
43 | } catch (e: Exception) {
44 | apps = emptyList()
45 | } finally {
46 | isLoading = false
47 | isRefreshing = false
48 | }
49 | }
50 | }
51 |
52 | LaunchedEffect(Unit) {
53 | loadApps()
54 | }
55 |
56 | Scaffold(
57 | topBar = {
58 | CenterAlignedTopAppBar(
59 | title = {
60 | Text("App Store", style = MaterialTheme.typography.headlineMedium)
61 | },
62 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
63 | containerColor = MaterialTheme.colorScheme.surface,
64 | scrolledContainerColor = MaterialTheme.colorScheme.surface
65 | ),
66 | actions = {
67 | IconButton(
68 | onClick = { showSearch = !showSearch },
69 | modifier = Modifier.size(40.dp)
70 | ) {
71 | Icon(
72 | Icons.Default.Search,
73 | contentDescription = "Search",
74 | tint = MaterialTheme.colorScheme.primary
75 | )
76 | }
77 | }
78 | )
79 | }
80 | ) { innerPadding ->
81 |
82 | Column(modifier = Modifier.padding(innerPadding)) {
83 | if (showSearch) {
84 | OutlinedTextField(
85 | value = searchQuery,
86 | onValueChange = { searchQuery = it },
87 | placeholder = { Text("Discover amazing apps...") },
88 | leadingIcon = {
89 | Icon(
90 | Icons.Default.Search,
91 | contentDescription = "Search",
92 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
93 | )
94 | },
95 | singleLine = true,
96 | modifier = Modifier
97 | .fillMaxWidth()
98 | .padding(spacing.medium),
99 | shape = RoundedCornerShape(spacing.medium)
100 | )
101 | }
102 |
103 | SwipeRefresh(
104 | state = remember { SwipeRefreshState(isRefreshing) },
105 | onRefresh = {
106 | isRefreshing = true
107 | loadApps()
108 | },
109 | modifier = Modifier.fillMaxSize()
110 | ) {
111 | when {
112 | isLoading -> {
113 | Box(
114 | modifier = Modifier.fillMaxSize(),
115 | contentAlignment = Alignment.Center
116 | ) {
117 | CircularProgressIndicator(
118 | color = MaterialTheme.colorScheme.primary
119 | )
120 | }
121 | }
122 |
123 | apps.isEmpty() -> {
124 | Box(
125 | modifier = Modifier.fillMaxSize(),
126 | contentAlignment = Alignment.Center
127 | ) {
128 | Column(
129 | horizontalAlignment = Alignment.CenterHorizontally,
130 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
131 | ) {
132 | Icon(
133 | Icons.Default.Search,
134 | contentDescription = "No apps",
135 | modifier = Modifier.size(48.dp),
136 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
137 | )
138 | Text(
139 | "No apps available",
140 | style = MaterialTheme.typography.bodyLarge,
141 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
142 | )
143 | }
144 | }
145 | }
146 |
147 | else -> {
148 | val filteredApps = if (searchQuery.isNotEmpty()) {
149 | apps.filter {
150 | it.name.contains(searchQuery, ignoreCase = true) ||
151 | it.description.contains(searchQuery, ignoreCase = true)
152 | }
153 | } else apps
154 |
155 | if (filteredApps.isEmpty()) {
156 | Box(
157 | modifier = Modifier.fillMaxSize(),
158 | contentAlignment = Alignment.Center
159 | ) {
160 | Column(
161 | horizontalAlignment = Alignment.CenterHorizontally,
162 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
163 | ) {
164 | Icon(
165 | Icons.Default.Search,
166 | contentDescription = "No results",
167 | modifier = Modifier.size(48.dp),
168 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
169 | )
170 | Text(
171 | "No results for \"$searchQuery\"",
172 | style = MaterialTheme.typography.bodyLarge,
173 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
174 | )
175 | }
176 | }
177 | } else {
178 | LazyColumn(
179 | modifier = Modifier
180 | .fillMaxSize()
181 | .padding(spacing.medium),
182 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
183 | ) {
184 | items(filteredApps) { app ->
185 | PremiumAppCard(app, navController)
186 | }
187 | }
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 | }
195 |
196 | @Composable
197 | fun PremiumAppCard(app: App, navController: NavController) {
198 | val spacing = LocalSpacing.current
199 |
200 | PremiumCard(
201 | onClick = { navController.navigate("appdetail/${app.id}") },
202 | modifier = Modifier.fillMaxWidth()
203 | ) {
204 | Row(
205 | modifier = Modifier
206 | .fillMaxWidth()
207 | .padding(spacing.medium),
208 | verticalAlignment = Alignment.CenterVertically
209 | ) {
210 | Image(
211 | painter = rememberAsyncImagePainter(app.logoUrl),
212 | contentDescription = app.name,
213 | modifier = Modifier
214 | .size(64.dp)
215 | .clip(RoundedCornerShape(spacing.medium))
216 | )
217 |
218 | Spacer(modifier = Modifier.width(spacing.medium))
219 |
220 | Column(
221 | modifier = Modifier.weight(1f)
222 | ) {
223 | Text(
224 | app.name,
225 | style = MaterialTheme.typography.titleMedium,
226 | color = MaterialTheme.colorScheme.onSurface,
227 | maxLines = 1
228 | )
229 |
230 | Spacer(modifier = Modifier.height(4.dp))
231 |
232 | Text(
233 | app.description.take(80) + if (app.description.length > 80) "..." else "",
234 | style = MaterialTheme.typography.bodySmall,
235 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
236 | maxLines = 2
237 | )
238 | }
239 |
240 | Spacer(modifier = Modifier.width(spacing.small))
241 |
242 | Icon(
243 | Icons.Default.ArrowForward,
244 | contentDescription = "View details",
245 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f),
246 | modifier = Modifier.size(20.dp)
247 | )
248 | }
249 | }
250 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/AppDetailScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import android.net.Uri
4 | import androidx.browser.customtabs.CustomTabsIntent
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.lazy.LazyRow
10 | import androidx.compose.foundation.lazy.items
11 | import androidx.compose.foundation.rememberScrollState
12 | import androidx.compose.foundation.shape.CircleShape
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.foundation.verticalScroll
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.filled.ArrowBack
17 | import androidx.compose.material.icons.filled.Download
18 | import androidx.compose.material.icons.filled.Search
19 | import androidx.compose.material3.*
20 | import androidx.compose.runtime.*
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.draw.clip
24 | import androidx.compose.ui.graphics.Color
25 | import androidx.compose.ui.layout.ContentScale
26 | import androidx.compose.ui.platform.LocalContext
27 | import androidx.compose.ui.unit.dp
28 | import androidx.navigation.NavController
29 | import coil.compose.rememberAsyncImagePainter
30 | import kotlinx.coroutines.launch
31 | import com.winlay.a3x.components.PremiumButton
32 | import com.winlay.a3x.ui.theme.LocalSpacing
33 |
34 | @OptIn(ExperimentalMaterial3Api::class)
35 | @Composable
36 | fun AppDetailScreen(appId: String, navController: NavController) {
37 | val context = LocalContext.current
38 | val scope = rememberCoroutineScope()
39 | val spacing = LocalSpacing.current
40 |
41 | var app by remember { mutableStateOf(null) }
42 | var isLoading by remember { mutableStateOf(true) }
43 | var selectedImage by remember { mutableStateOf(null) }
44 |
45 | LaunchedEffect(appId) {
46 | val apps = AppRepository.getApps()
47 | app = apps.find { it.id == appId }
48 | isLoading = false
49 | }
50 |
51 | if (isLoading) {
52 | Box(
53 | modifier = Modifier.fillMaxSize(),
54 | contentAlignment = Alignment.Center
55 | ) {
56 | CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
57 | }
58 | return
59 | }
60 |
61 | if (app == null) {
62 | Column(
63 | modifier = Modifier
64 | .fillMaxSize()
65 | .padding(spacing.large),
66 | verticalArrangement = Arrangement.Center,
67 | horizontalAlignment = Alignment.CenterHorizontally
68 | ) {
69 | Icon(
70 | Icons.Default.Search,
71 | contentDescription = "Not found",
72 | modifier = Modifier.size(48.dp),
73 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
74 | )
75 | Spacer(modifier = Modifier.height(spacing.medium))
76 | Text("App not found.", style = MaterialTheme.typography.bodyLarge)
77 | Spacer(modifier = Modifier.height(spacing.medium))
78 | PremiumButton(
79 | onClick = { navController.popBackStack() },
80 | text = "Go Back"
81 | )
82 | }
83 | return
84 | }
85 |
86 | Scaffold(
87 | topBar = {
88 | CenterAlignedTopAppBar(
89 | title = {
90 | Text(
91 | app!!.name,
92 | style = MaterialTheme.typography.titleLarge,
93 | maxLines = 1
94 | )
95 | },
96 | navigationIcon = {
97 | IconButton(
98 | onClick = { navController.popBackStack() },
99 | modifier = Modifier.size(40.dp)
100 | ) {
101 | Icon(
102 | Icons.Filled.ArrowBack,
103 | contentDescription = "Back",
104 | tint = MaterialTheme.colorScheme.primary
105 | )
106 | }
107 | },
108 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
109 | containerColor = MaterialTheme.colorScheme.surface,
110 | scrolledContainerColor = MaterialTheme.colorScheme.surface
111 | )
112 | )
113 | },
114 | bottomBar = {
115 | Surface(
116 | tonalElevation = 8.dp,
117 | shadowElevation = 4.dp
118 | ) {
119 | Row(
120 | modifier = Modifier
121 | .fillMaxWidth()
122 | .padding(spacing.medium),
123 | horizontalArrangement = Arrangement.Center
124 | ) {
125 | PremiumButton(
126 | onClick = {
127 | val intent = CustomTabsIntent.Builder().build()
128 | intent.launchUrl(context, Uri.parse(app!!.downloadUrl))
129 | },
130 | text = "Download",
131 | modifier = Modifier.fillMaxWidth(0.8f)
132 | )
133 | }
134 | }
135 | }
136 | ) { innerPadding ->
137 | Column(
138 | modifier = Modifier
139 | .padding(innerPadding)
140 | .verticalScroll(rememberScrollState())
141 | .fillMaxSize(),
142 | horizontalAlignment = Alignment.CenterHorizontally
143 | ) {
144 | Box(
145 | modifier = Modifier
146 | .fillMaxWidth()
147 | .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
148 | .padding(spacing.xlarge),
149 | contentAlignment = Alignment.Center
150 | ) {
151 | Image(
152 | painter = rememberAsyncImagePainter(app!!.logoUrl),
153 | contentDescription = app!!.name,
154 | modifier = Modifier
155 | .size(120.dp)
156 | .clip(RoundedCornerShape(32.dp))
157 | .clickable { selectedImage = app!!.logoUrl },
158 | contentScale = ContentScale.Crop
159 | )
160 | }
161 |
162 | Column(
163 | modifier = Modifier
164 | .fillMaxWidth()
165 | .padding(spacing.medium),
166 | horizontalAlignment = Alignment.Start
167 | ) {
168 | Text(
169 | text = app!!.name,
170 | style = MaterialTheme.typography.headlineSmall,
171 | color = MaterialTheme.colorScheme.onSurface
172 | )
173 |
174 | Spacer(modifier = Modifier.height(spacing.medium))
175 |
176 | var expanded by remember { mutableStateOf(false) }
177 | val shortDescription = app!!.description.take(200)
178 | val isLong = app!!.description.length > 200
179 |
180 | Text(
181 | text = if (expanded || !isLong) app!!.description else "$shortDescription...",
182 | style = MaterialTheme.typography.bodyLarge,
183 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
184 | )
185 |
186 | if (isLong) {
187 | TextButton(
188 | onClick = { expanded = !expanded },
189 | colors = ButtonDefaults.textButtonColors(
190 | contentColor = MaterialTheme.colorScheme.primary
191 | )
192 | ) {
193 | Text(if (expanded) "Read Less" else "Read More")
194 | }
195 | }
196 |
197 | if (app!!.screenshots.isNotEmpty()) {
198 | Spacer(modifier = Modifier.height(spacing.large))
199 |
200 | Text(
201 | text = "Screenshots",
202 | style = MaterialTheme.typography.titleMedium,
203 | color = MaterialTheme.colorScheme.onSurface
204 | )
205 |
206 | Spacer(modifier = Modifier.height(spacing.small))
207 |
208 | LazyRow(
209 | horizontalArrangement = Arrangement.spacedBy(spacing.medium),
210 | modifier = Modifier.fillMaxWidth()
211 | ) {
212 | items(app!!.screenshots) { screenshot ->
213 | Image(
214 | painter = rememberAsyncImagePainter(screenshot),
215 | contentDescription = null,
216 | modifier = Modifier
217 | .height(200.dp)
218 | .aspectRatio(9f / 16f)
219 | .clip(RoundedCornerShape(spacing.medium))
220 | .clickable { selectedImage = screenshot },
221 | contentScale = ContentScale.Crop
222 | )
223 | }
224 | }
225 | }
226 | }
227 | }
228 |
229 | selectedImage?.let { imageUrl ->
230 | androidx.compose.ui.window.Dialog(onDismissRequest = { selectedImage = null }) {
231 | Box(
232 | modifier = Modifier
233 | .fillMaxSize()
234 | .background(Color.Black.copy(alpha = 0.9f)),
235 | contentAlignment = Alignment.Center
236 | ) {
237 | Image(
238 | painter = rememberAsyncImagePainter(imageUrl),
239 | contentDescription = null,
240 | contentScale = ContentScale.Fit,
241 | modifier = Modifier
242 | .fillMaxWidth()
243 | .fillMaxHeight(0.8f)
244 | )
245 |
246 | IconButton(
247 | onClick = { selectedImage = null },
248 | modifier = Modifier
249 | .align(Alignment.TopEnd)
250 | .padding(20.dp)
251 | .background(Color.Black.copy(alpha = 0.5f), CircleShape)
252 | .size(40.dp)
253 | ) {
254 | Icon(
255 | imageVector = Icons.Default.ArrowBack,
256 | contentDescription = "Close",
257 | tint = Color.White
258 | )
259 | }
260 | }
261 | }
262 | }
263 | }
264 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/EventScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import android.util.Log
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.rememberScrollState
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.foundation.verticalScroll
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.filled.ArrowBack
13 | import androidx.compose.material3.*
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.layout.ContentScale
19 | import androidx.compose.ui.platform.LocalContext
20 | import androidx.compose.ui.unit.dp
21 | import androidx.navigation.NavController
22 | import coil.compose.rememberAsyncImagePainter
23 | import coil.request.ImageRequest
24 | import kotlinx.coroutines.Dispatchers
25 | import kotlinx.coroutines.launch
26 | import kotlinx.serialization.Serializable
27 | import kotlinx.serialization.decodeFromString
28 | import kotlinx.serialization.json.Json
29 | import kotlinx.serialization.json.jsonObject
30 | import java.net.URL
31 | import java.time.*
32 | import java.time.format.TextStyle
33 | import java.util.*
34 | import com.winlay.a3x.components.PremiumCard
35 | import com.winlay.a3x.ui.theme.LocalSpacing
36 | import androidx.compose.foundation.background
37 | import androidx.compose.material.icons.filled.Event
38 | import androidx.compose.ui.draw.clip
39 | import androidx.compose.material3.ExperimentalMaterial3Api
40 |
41 | @OptIn(ExperimentalMaterial3Api::class)
42 | @Composable
43 | fun EventScreen(navController: NavController) {
44 | val scope = rememberCoroutineScope()
45 | val today = LocalDate.now()
46 | var events by remember { mutableStateOf>(emptyList()) }
47 | var selectedDate by remember { mutableStateOf(today) }
48 | var selectedEvents by remember { mutableStateOf>(emptyList()) }
49 | var currentMonth by remember { mutableStateOf(YearMonth.now()) }
50 | val daysOfWeek = DayOfWeek.values()
51 | val spacing = LocalSpacing.current
52 |
53 | LaunchedEffect(Unit) {
54 | scope.launch(Dispatchers.IO) {
55 | try {
56 | val jsonString = URL("https://winlayassets.a3x.xyz/json/events.json").readText()
57 | val root = Json.parseToJsonElement(jsonString).jsonObject
58 | val parsedEvents = Json.decodeFromString>(root["events"].toString())
59 | events = parsedEvents
60 | selectedEvents = parsedEvents.filter { it.date == selectedDate.toString() }
61 | } catch (e: Exception) {
62 | Log.e("EventScreen", "Failed to fetch events", e)
63 | }
64 | }
65 | }
66 |
67 | Scaffold(
68 | topBar = {
69 | CenterAlignedTopAppBar(
70 | title = {
71 | Text(
72 | "Events Calendar",
73 | style = MaterialTheme.typography.headlineSmall
74 | )
75 | },
76 | navigationIcon = {
77 | IconButton(
78 | onClick = { navController.popBackStack() },
79 | modifier = Modifier.size(40.dp)
80 | ) {
81 | Icon(
82 | Icons.Default.ArrowBack,
83 | contentDescription = "Back",
84 | tint = MaterialTheme.colorScheme.primary
85 | )
86 | }
87 | },
88 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
89 | containerColor = MaterialTheme.colorScheme.surface,
90 | scrolledContainerColor = MaterialTheme.colorScheme.surface
91 | )
92 | )
93 | }
94 | ) { innerPadding ->
95 | Column(
96 | modifier = Modifier
97 | .padding(innerPadding)
98 | .padding(spacing.medium)
99 | .verticalScroll(rememberScrollState())
100 | .fillMaxSize()
101 | ) {
102 | PremiumCalendarHeader(
103 | currentMonth = currentMonth,
104 | onPreviousMonth = { currentMonth = currentMonth.minusMonths(1) },
105 | onNextMonth = { currentMonth = currentMonth.plusMonths(1) }
106 | )
107 |
108 | Spacer(modifier = Modifier.height(spacing.medium))
109 |
110 | DaysOfWeekHeader(daysOfWeek)
111 |
112 | Spacer(modifier = Modifier.height(spacing.small))
113 |
114 | PremiumCalendarGrid(
115 | currentMonth = currentMonth,
116 | selectedDate = selectedDate,
117 | events = events,
118 | onDateSelected = { date ->
119 | selectedDate = date
120 | selectedEvents = events.filter { it.date == date.toString() }
121 | }
122 | )
123 |
124 | Spacer(modifier = Modifier.height(spacing.large))
125 |
126 | Text(
127 | "Events on ${selectedDate}",
128 | style = MaterialTheme.typography.titleMedium,
129 | color = MaterialTheme.colorScheme.onSurface,
130 | modifier = Modifier.padding(bottom = spacing.small)
131 | )
132 |
133 | if (selectedEvents.isEmpty()) {
134 | Box(
135 | modifier = Modifier
136 | .fillMaxWidth()
137 | .padding(vertical = spacing.xlarge),
138 | contentAlignment = Alignment.Center
139 | ) {
140 | Column(
141 | horizontalAlignment = Alignment.CenterHorizontally,
142 | verticalArrangement = Arrangement.spacedBy(spacing.small)
143 | ) {
144 | Icon(
145 | Icons.Default.Event,
146 | contentDescription = "No events",
147 | modifier = Modifier.size(32.dp),
148 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
149 | )
150 | Text(
151 | "No events scheduled",
152 | style = MaterialTheme.typography.bodyMedium,
153 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
154 | )
155 | }
156 | }
157 | } else {
158 | Column(verticalArrangement = Arrangement.spacedBy(spacing.medium)) {
159 | selectedEvents.forEach { event ->
160 | PremiumEventCard(event)
161 | }
162 | }
163 | }
164 | }
165 | }
166 | }
167 |
168 | @Composable
169 | fun PremiumCalendarHeader(
170 | currentMonth: YearMonth,
171 | onPreviousMonth: () -> Unit,
172 | onNextMonth: () -> Unit
173 | ) {
174 | val spacing = LocalSpacing.current
175 |
176 | Row(
177 | modifier = Modifier.fillMaxWidth(),
178 | horizontalArrangement = Arrangement.SpaceBetween,
179 | verticalAlignment = Alignment.CenterVertically
180 | ) {
181 | TextButton(
182 | onClick = onPreviousMonth,
183 | colors = ButtonDefaults.textButtonColors(
184 | contentColor = MaterialTheme.colorScheme.primary
185 | )
186 | ) {
187 | Text("‹ ${currentMonth.minusMonths(1).month.getDisplayName(TextStyle.SHORT, Locale.getDefault())}")
188 | }
189 |
190 | Text(
191 | text = "${currentMonth.month.getDisplayName(TextStyle.FULL, Locale.getDefault())} ${currentMonth.year}",
192 | style = MaterialTheme.typography.titleLarge,
193 | color = MaterialTheme.colorScheme.onSurface
194 | )
195 |
196 | TextButton(
197 | onClick = onNextMonth,
198 | colors = ButtonDefaults.textButtonColors(
199 | contentColor = MaterialTheme.colorScheme.primary
200 | )
201 | ) {
202 | Text("${currentMonth.plusMonths(1).month.getDisplayName(TextStyle.SHORT, Locale.getDefault())} ›")
203 | }
204 | }
205 | }
206 |
207 | @Composable
208 | fun DaysOfWeekHeader(daysOfWeek: Array) {
209 | val spacing = LocalSpacing.current
210 |
211 | Row(
212 | modifier = Modifier.fillMaxWidth(),
213 | horizontalArrangement = Arrangement.SpaceBetween
214 | ) {
215 | daysOfWeek.forEach {
216 | Text(
217 | text = it.getDisplayName(TextStyle.SHORT, Locale.getDefault()),
218 | modifier = Modifier.weight(1f),
219 | style = MaterialTheme.typography.labelSmall,
220 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
221 | textAlign = androidx.compose.ui.text.style.TextAlign.Center
222 | )
223 | }
224 | }
225 | }
226 |
227 | @Composable
228 | fun PremiumCalendarGrid(
229 | currentMonth: YearMonth,
230 | selectedDate: LocalDate,
231 | events: List,
232 | onDateSelected: (LocalDate) -> Unit
233 | ) {
234 | val spacing = LocalSpacing.current
235 | val firstDayOfMonth = currentMonth.atDay(1)
236 | val dayOfWeekOffset = firstDayOfMonth.dayOfWeek.ordinal
237 | val daysInMonth = currentMonth.lengthOfMonth()
238 | val totalGridCells = dayOfWeekOffset + daysInMonth
239 | val weeks = (totalGridCells + 6) / 7
240 |
241 | Column {
242 | for (week in 0 until weeks) {
243 | Row(
244 | modifier = Modifier.fillMaxWidth(),
245 | horizontalArrangement = Arrangement.SpaceBetween
246 | ) {
247 | for (dayOfWeek in 0..6) {
248 | val dayIndex = week * 7 + dayOfWeek
249 | val day = dayIndex - dayOfWeekOffset + 1
250 |
251 | if (day in 1..daysInMonth) {
252 | val date = currentMonth.atDay(day)
253 | val isSelected = selectedDate == date
254 | val hasEvent = events.any { it.date == date.toString() }
255 |
256 | Box(
257 | contentAlignment = Alignment.Center,
258 | modifier = Modifier
259 | .weight(1f)
260 | .aspectRatio(1f)
261 | .padding(2.dp)
262 | .background(
263 | if (isSelected) MaterialTheme.colorScheme.primary.copy(0.2f)
264 | else Color.Transparent,
265 | CircleShape
266 | )
267 | .clickable { onDateSelected(date) }
268 | ) {
269 | Column(
270 | horizontalAlignment = Alignment.CenterHorizontally,
271 | verticalArrangement = Arrangement.spacedBy(2.dp)
272 | ) {
273 | Text(
274 | day.toString(),
275 | style = MaterialTheme.typography.labelMedium,
276 | color = if (isSelected) MaterialTheme.colorScheme.primary
277 | else MaterialTheme.colorScheme.onSurface
278 | )
279 | if (hasEvent) {
280 | Box(
281 | modifier = Modifier
282 | .size(4.dp)
283 | .background(MaterialTheme.colorScheme.primary, CircleShape)
284 | )
285 | }
286 | }
287 | }
288 | } else {
289 | Spacer(modifier = Modifier.weight(1f))
290 | }
291 | }
292 | }
293 | }
294 | }
295 | }
296 |
297 | @Composable
298 | fun PremiumEventCard(event: Event) {
299 | val spacing = LocalSpacing.current
300 |
301 | PremiumCard(
302 | modifier = Modifier.fillMaxWidth()
303 | ) {
304 | Column(
305 | modifier = Modifier
306 | .fillMaxWidth()
307 | .padding(spacing.medium)
308 | ) {
309 | Image(
310 | painter = rememberAsyncImagePainter(
311 | ImageRequest.Builder(LocalContext.current)
312 | .data(event.image)
313 | .crossfade(true)
314 | .build()
315 | ),
316 | contentDescription = null,
317 | contentScale = ContentScale.Crop,
318 | modifier = Modifier
319 | .fillMaxWidth()
320 | .height(180.dp)
321 | .clip(MaterialTheme.shapes.medium)
322 | )
323 |
324 | Spacer(modifier = Modifier.height(spacing.medium))
325 |
326 | Text(
327 | event.title,
328 | style = MaterialTheme.typography.titleMedium,
329 | color = MaterialTheme.colorScheme.onSurface
330 | )
331 |
332 | Spacer(modifier = Modifier.height(spacing.small))
333 |
334 | Text(
335 | event.description,
336 | style = MaterialTheme.typography.bodyMedium,
337 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
338 | )
339 |
340 | Spacer(modifier = Modifier.height(spacing.small))
341 |
342 | Text(
343 | "Date: ${event.date}",
344 | style = MaterialTheme.typography.labelSmall,
345 | color = MaterialTheme.colorScheme.primary
346 | )
347 | }
348 | }
349 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/DetailScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.foundation.lazy.items
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.filled.ArrowBack
12 | import androidx.compose.material.icons.filled.Search
13 | import androidx.compose.material3.*
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.layout.ContentScale
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.unit.dp
20 | import androidx.navigation.NavController
21 | import coil.compose.rememberAsyncImagePainter
22 | import coil.request.ImageRequest
23 | import kotlinx.coroutines.Dispatchers
24 | import kotlinx.coroutines.withContext
25 | import org.json.JSONArray
26 | import java.net.URL
27 | import com.winlay.a3x.components.PremiumCard
28 | import com.winlay.a3x.ui.theme.LocalSpacing
29 | import androidx.compose.foundation.background
30 | import androidx.compose.ui.draw.clip
31 | import androidx.compose.foundation.shape.RoundedCornerShape
32 | import com.winlay.a3x.ui.theme.WinlayError
33 | import androidx.compose.material.icons.filled.Error
34 |
35 |
36 | @Composable
37 | fun DetailScreen(name: String, navController: NavController) {
38 | val spacing = LocalSpacing.current
39 |
40 | Column(
41 | modifier = Modifier
42 | .fillMaxSize()
43 | .padding(spacing.medium)
44 | ) {
45 | Row(
46 | verticalAlignment = Alignment.CenterVertically,
47 | modifier = Modifier
48 | .fillMaxWidth()
49 | .padding(bottom = spacing.large)
50 | ) {
51 | IconButton(
52 | onClick = { navController.navigateUp() },
53 | modifier = Modifier
54 | .size(48.dp)
55 | .background(
56 | MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
57 | MaterialTheme.shapes.small
58 | )
59 | ) {
60 | Icon(
61 | Icons.Filled.ArrowBack,
62 | contentDescription = "Back",
63 | tint = MaterialTheme.colorScheme.primary
64 | )
65 | }
66 | Spacer(modifier = Modifier.width(spacing.medium))
67 | Text(
68 | name,
69 | style = MaterialTheme.typography.headlineMedium,
70 | color = MaterialTheme.colorScheme.onSurface
71 | )
72 | }
73 |
74 | when (name) {
75 | "Windows" -> PremiumProductList("Windows")
76 | "Linux" -> PremiumProductList("Linux")
77 | "Android" -> PremiumProductList("Android")
78 | else -> {
79 | Box(
80 | modifier = Modifier.fillMaxSize(),
81 | contentAlignment = Alignment.Center
82 | ) {
83 | Column(
84 | horizontalAlignment = Alignment.CenterHorizontally,
85 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
86 | ) {
87 | Icon(
88 | Icons.Default.Search,
89 | contentDescription = "Coming soon",
90 | modifier = Modifier.size(48.dp),
91 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
92 | )
93 | Text(
94 | "Coming soon",
95 | style = MaterialTheme.typography.bodyLarge,
96 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
97 | )
98 | }
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | @Composable
106 | fun PremiumProductList(type: String) {
107 | var products by remember { mutableStateOf>(emptyList()) }
108 | var query by remember { mutableStateOf("") }
109 | var error by remember { mutableStateOf(null) }
110 | var showSearch by remember { mutableStateOf(false) }
111 | val spacing = LocalSpacing.current
112 |
113 | LaunchedEffect(type) {
114 | try {
115 | val jsonString = withContext(Dispatchers.IO) {
116 | URL("https://winlayassets.a3x.xyz/json/${type.lowercase()}.json").readText()
117 | }
118 | val jsonArray = JSONArray(jsonString)
119 | products = (0 until jsonArray.length()).map { i ->
120 | val obj = jsonArray.getJSONObject(i)
121 | Product(
122 | name = obj.getString("name"),
123 | description = obj.getString("description"),
124 | iconUrl = obj.getString("iconUrl"),
125 | downloads = obj.getJSONArray("downloads").let { downloadsArray ->
126 | (0 until downloadsArray.length()).map { j ->
127 | val d = downloadsArray.getJSONObject(j)
128 | DownloadLink(d.getString("label"), d.getString("url"))
129 | }
130 | }
131 | )
132 | }
133 | } catch (e: Exception) {
134 | error = "Failed to load products. Please check your connection."
135 | }
136 | }
137 |
138 | val filteredProducts = products.filter {
139 | val trimmedQuery = query.trim()
140 | it.name.contains(trimmedQuery, ignoreCase = true) ||
141 | it.description.contains(trimmedQuery, ignoreCase = true)
142 | }
143 |
144 | Column {
145 | Row(
146 | modifier = Modifier
147 | .fillMaxWidth()
148 | .padding(bottom = spacing.medium),
149 | verticalAlignment = Alignment.CenterVertically,
150 | horizontalArrangement = Arrangement.SpaceBetween
151 | ) {
152 | Text(
153 | text = "$type Products",
154 | style = MaterialTheme.typography.headlineSmall,
155 | color = MaterialTheme.colorScheme.onSurface
156 | )
157 |
158 | IconButton(
159 | onClick = { showSearch = !showSearch },
160 | modifier = Modifier.size(40.dp)
161 | ) {
162 | Icon(
163 | Icons.Default.Search,
164 | contentDescription = "Search",
165 | tint = MaterialTheme.colorScheme.primary
166 | )
167 | }
168 | }
169 |
170 | if (showSearch) {
171 | OutlinedTextField(
172 | value = query,
173 | onValueChange = { query = it },
174 | placeholder = { Text("Search $type products...") },
175 | leadingIcon = {
176 | Icon(
177 | Icons.Default.Search,
178 | contentDescription = "Search",
179 | tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
180 | )
181 | },
182 | singleLine = true,
183 | modifier = Modifier
184 | .fillMaxWidth()
185 | .padding(bottom = spacing.medium),
186 | shape = RoundedCornerShape(spacing.medium)
187 | )
188 | }
189 |
190 | when {
191 | error != null -> {
192 | Box(
193 | modifier = Modifier.fillMaxSize(),
194 | contentAlignment = Alignment.Center
195 | ) {
196 | Column(
197 | horizontalAlignment = Alignment.CenterHorizontally,
198 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
199 | ) {
200 | Icon(
201 | Icons.Default.Error,
202 | contentDescription = "Error",
203 | modifier = Modifier.size(48.dp),
204 | tint = WinlayError
205 | )
206 | Text(
207 | error ?: "Error",
208 | style = MaterialTheme.typography.bodyLarge,
209 | color = WinlayError
210 | )
211 | }
212 | }
213 | }
214 |
215 | products.isEmpty() -> {
216 | Box(
217 | modifier = Modifier.fillMaxSize(),
218 | contentAlignment = Alignment.Center
219 | ) {
220 | CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
221 | }
222 | }
223 |
224 | else -> {
225 | if (filteredProducts.isEmpty()) {
226 | Box(
227 | modifier = Modifier.fillMaxSize(),
228 | contentAlignment = Alignment.Center
229 | ) {
230 | Column(
231 | horizontalAlignment = Alignment.CenterHorizontally,
232 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
233 | ) {
234 | Icon(
235 | Icons.Default.Search,
236 | contentDescription = "No results",
237 | modifier = Modifier.size(48.dp),
238 | tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
239 | )
240 | Text(
241 | "No results found for \"${query.trim()}\"",
242 | style = MaterialTheme.typography.bodyLarge,
243 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
244 | )
245 | }
246 | }
247 | } else {
248 | LazyColumn(
249 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
250 | ) {
251 | items(filteredProducts) { product ->
252 | PremiumProductCard(product)
253 | }
254 | }
255 | }
256 | }
257 | }
258 | }
259 | }
260 |
261 | @Composable
262 | fun PremiumProductCard(product: Product) {
263 | val context = LocalContext.current
264 | val spacing = LocalSpacing.current
265 |
266 | PremiumCard(
267 | modifier = Modifier.fillMaxWidth()
268 | ) {
269 | Column(
270 | modifier = Modifier
271 | .fillMaxWidth()
272 | .padding(spacing.medium)
273 | ) {
274 | Row(
275 | verticalAlignment = Alignment.CenterVertically,
276 | modifier = Modifier.fillMaxWidth()
277 | ) {
278 | Image(
279 | painter = rememberAsyncImagePainter(
280 | ImageRequest.Builder(context)
281 | .data(product.iconUrl)
282 | .crossfade(true)
283 | .build()
284 | ),
285 | contentDescription = product.name,
286 | modifier = Modifier
287 | .size(56.dp)
288 | .clip(MaterialTheme.shapes.medium),
289 | contentScale = ContentScale.Fit
290 | )
291 |
292 | Spacer(modifier = Modifier.width(spacing.medium))
293 |
294 | Text(
295 | product.name,
296 | style = MaterialTheme.typography.titleMedium,
297 | color = MaterialTheme.colorScheme.onSurface,
298 | modifier = Modifier.weight(1f)
299 | )
300 | }
301 |
302 | Spacer(modifier = Modifier.height(spacing.medium))
303 |
304 | var expanded by remember { mutableStateOf(false) }
305 | val preview = if (product.description.length > 120 && !expanded) {
306 | product.description.take(120) + "..."
307 | } else {
308 | product.description
309 | }
310 |
311 | Text(
312 | preview,
313 | style = MaterialTheme.typography.bodyMedium,
314 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
315 | )
316 |
317 | if (product.description.length > 120) {
318 | TextButton(
319 | onClick = { expanded = !expanded },
320 | colors = ButtonDefaults.textButtonColors(
321 | contentColor = MaterialTheme.colorScheme.primary
322 | )
323 | ) {
324 | Text(if (expanded) "Show Less" else "Show More")
325 | }
326 | }
327 |
328 | Spacer(modifier = Modifier.height(spacing.medium))
329 |
330 | Text(
331 | "Download Options:",
332 | style = MaterialTheme.typography.labelLarge,
333 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.9f),
334 | modifier = Modifier.padding(bottom = spacing.small)
335 | )
336 |
337 | Column(
338 | verticalArrangement = Arrangement.spacedBy(spacing.small)
339 | ) {
340 | product.downloads.forEach { download ->
341 | OutlinedButton(
342 | onClick = {
343 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(download.url))
344 | context.startActivity(intent)
345 | },
346 | modifier = Modifier.fillMaxWidth(),
347 | shape = MaterialTheme.shapes.small,
348 | colors = ButtonDefaults.outlinedButtonColors(
349 | contentColor = MaterialTheme.colorScheme.primary
350 | )
351 | ) {
352 | Text(download.label)
353 | }
354 | }
355 | }
356 | }
357 | }
358 | }
--------------------------------------------------------------------------------
/json/windows.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Windows 11 Pro (Tiny 11 Core Beta)",
4 | "description": "No description",
5 | "iconUrl": "https://winlayassets.a3x.xyz/images/20250911_195104.jpg",
6 | "downloads": [
7 | {
8 | "label": "Download (Mediafire)",
9 | "url": "https://www.mediafire.com/file/5v6v53ug3afnzc4/tiny11.rar/file"
10 | }
11 | ]
12 | },
13 | {
14 | "name": "Windows 10 Enterprise LTSC 1809 LiteOS",
15 | "description": "No description",
16 | "iconUrl": "https://winlayassets.a3x.xyz/images/20250601_213610.jpg",
17 | "downloads": [
18 | {
19 | "label": "Download (Google Drive)",
20 | "url": "https://drive.google.com/file/d/1qwrZMP4S1_huQEC-3KySXKasSjh6SPuv/view?usp=drivesdk"
21 | }
22 | ]
23 | },
24 | {
25 | "name": "Windows 7 Pro LiteOS",
26 | "description": "No description",
27 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230505-112814_orig.jpg",
28 | "downloads": [
29 | {
30 | "label": "Download (Mediafire)",
31 | "url": "https://www.mediafire.com/file/rxo6pv9oswgsdr4/Windows_7_Lite_OS_by_%2540TheRimvydop.rar/file"
32 | }
33 | ]
34 | },
35 | {
36 | "name": "Windows 8.1 Pro Original",
37 | "description": "No description",
38 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230521-130552-orig_orig.jpg",
39 | "downloads": [
40 | {
41 | "label": "Download (Dropbox)",
42 | "url": "https://www.dropbox.com/s/hlu6gjvnjop0nrv/Windows%208.1%20Pro.rar?dl=0"
43 | }
44 | ]
45 | },
46 | {
47 | "name": "Windows 10 Enterprise",
48 | "description": "No description",
49 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230524-132532-orig_orig.jpg",
50 | "downloads": [
51 | {
52 | "label": "Download (Mediafire)",
53 | "url": "https://www.mediafire.com/file/h64eudj749tl3md/Windows_10_Enterprise.rar/file"
54 | }
55 | ]
56 | },
57 | {
58 | "name": "Windows 11 LTSC",
59 | "description": "No description",
60 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230521-130153_orig.jpg",
61 | "downloads": [
62 | {
63 | "label": "Download (Google Drive)",
64 | "url": "https://drive.google.com/file/d/165BrmMtg5JXjTKPIU-qjPaEOK7dRUvH1/view?usp=drivesdk"
65 | }
66 | ]
67 | },
68 | {
69 | "name": "Windows 10 Enterprise LTSC LiteOS",
70 | "description": "No description",
71 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231108-193153_orig.jpg",
72 | "downloads": [
73 | {
74 | "label": "Download (Pixeldrain)",
75 | "url": "https://pixeldrain.com/u/x36YBb4W"
76 | }
77 | ]
78 | },
79 | {
80 | "name": "Windows 10 Enterprise LTSC LiteOS",
81 | "description": "No description",
82 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231108-193153_orig.jpg",
83 | "downloads": [
84 | {
85 | "label": "Download (Pixeldrain)",
86 | "url": "https://pixeldrain.com/u/x36YBb4W"
87 | }
88 | ]
89 | },
90 | {
91 | "name": "Windows 10 S",
92 | "description": "No description",
93 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231016-154140_orig.jpg",
94 | "downloads": [
95 | {
96 | "label": "Download (Pixeldrain)",
97 | "url": "https://pixeldrain.com/u/Szm2YfcS"
98 | }
99 | ]
100 | },
101 | {
102 | "name": "Windows 10 Pro Metroless",
103 | "description": "No description",
104 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231016-154020_orig.jpg",
105 | "downloads": [
106 | {
107 | "label": "Download (Google Drive)",
108 | "url": "https://drive.google.com/file/d/1VAKPHZVKMN8hlNbjwe2jcNKRH4T_wsRl/view?usp=drivesdk"
109 | }
110 | ]
111 | },
112 | {
113 | "name": "Windows 8 Pro LiteOS",
114 | "description": "No description",
115 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231016-153949_orig.jpg",
116 | "downloads": [
117 | {
118 | "label": "Download (Mediafire)",
119 | "url": "https://www.mediafire.com/file/134ppjvff6z5q58/Windows_8.rar/file"
120 | }
121 | ]
122 | },
123 | {
124 | "name": "Windows 10 Mobile",
125 | "description": "No description",
126 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231013-184352_orig.jpg",
127 | "downloads": [
128 | {
129 | "label": "Download (Archive)",
130 | "url": "https://archive.org/details/Windows10MobileBuild14393"
131 | }
132 | ]
133 | },
134 | {
135 | "name": "Windows 11 Enterprise 21H2 (Tiny 11)",
136 | "description": "No description",
137 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230908-191531_orig.jpg",
138 | "downloads": [
139 | {
140 | "label": "Download (Mediafire)",
141 | "url": "https://www.mediafire.com/file/yyl4hzase992b1q/Tiny_11.rar/file"
142 | }
143 | ]
144 | },
145 | {
146 | "name": "Windows 10 Pro (Nexus LiteOS 1709)",
147 | "description": "No description",
148 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230908-190428_orig.jpg",
149 | "downloads": [
150 | {
151 | "label": "Download (Pixeldrain)",
152 | "url": "https://pixeldrain.com/u/f5Sbxg4F"
153 | }
154 | ]
155 | },
156 | {
157 | "name": "Windows XP 2021 Edition",
158 | "description": "No description",
159 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230830-095036_orig.jpg",
160 | "downloads": [
161 | {
162 | "label": "Download (Google Drive)",
163 | "url": "https://drive.google.com/file/d/1qvUsDPYKIab22ko265Hkb8F3sWdaIBui/view?usp=drivesdk"
164 | }
165 | ]
166 | },
167 | {
168 | "name": "Windows 10 Pro 1909 Dark Edition",
169 | "description": "No description",
170 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230830-094921_orig.jpg",
171 | "downloads": [
172 | {
173 | "label": "Download (Mega)",
174 | "url": "https://mega.nz/file/JwpGQTqT#txRYCjVjwyzfO0Knrh21idCKauvjOWclUdldXnnkrQM"
175 | }
176 | ]
177 | },
178 | {
179 | "name": "Windows 7 Starter",
180 | "description": "No description",
181 | "iconUrl": "https://winlayassets.a3x.xyz/images/windows-7-starter-sp1_orig.jpg",
182 | "downloads": [
183 | {
184 | "label": "Download (Mega)",
185 | "url": "https://mega.nz/file/U84HCKSD#1MoPFGK96VAtJhxM-8IRSMEL60HyC_rmBamWRj-sCAY"
186 | }
187 | ]
188 | },
189 | {
190 | "name": "Windows 8.1 Pro Superlite",
191 | "description": "No description",
192 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230830-094246_orig.jpg",
193 | "downloads": [
194 | {
195 | "label": "Download (Mediafire)",
196 | "url": "https://www.mediafire.com/file/ve0ijqe44t6tn7s/Windows+8.1+Pro+Superlite+by+@Rimvydop.rar/file"
197 | }
198 | ]
199 | },
200 | {
201 | "name": "Windows 7 Superlite",
202 | "description": "No description",
203 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230830-093940_orig.jpg",
204 | "downloads": [
205 | {
206 | "label": "Download (Mediafire)",
207 | "url": "https://www.mediafire.com/file/pp41u2cjlivuone/Windows_7_Superlite_by_%2540Rimvydop.rar/file"
208 | }
209 | ]
210 | },
211 | {
212 | "name": "Windows 10 Pro Superlite v6",
213 | "description": "No description",
214 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230829-193618_orig.jpg",
215 | "downloads": [
216 | {
217 | "label": "Download (Mediafire)",
218 | "url": "https://www.mediafire.com/file/zklq6nftuchlidq/Windows_10_Pro_Superlite_V6_by_%2540TheRimvydop.rar/file"
219 | }
220 | ]
221 | },
222 | {
223 | "name": "Windows 7 Enterprise",
224 | "description": "No description",
225 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230505-112700_orig.jpg",
226 | "downloads": [
227 | {
228 | "label": "Download (Mediafire)",
229 | "url": "https://www.mediafire.com/file/50flher943s4qfy/Windows_7_Enterprise.rar/file"
230 | }
231 | ]
232 | },
233 | {
234 | "name": "Windows 11 Pro (Nexus Tiny 11)",
235 | "description": "No description",
236 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230505-125023_orig.jpg",
237 | "downloads": [
238 | {
239 | "label": "Download (Mediafire)",
240 | "url": "https://www.mediafire.com/file/fsx49883t5ukxpt/Nexus_Tiny_Lite_OS_11_by_%2540TheRimvydop.rar/file?dkey=52y5fivnrju&r=332"
241 | }
242 | ]
243 | },
244 | {
245 | "name": "Windows 11 Pro 22H2 LiteOS",
246 | "description": "No description",
247 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230505-125414_orig.jpg",
248 | "downloads": [
249 | {
250 | "label": "Download (Mediafire)",
251 | "url": "https://www.mediafire.com/file/422v5n8hlb8omnx/Windows_11_Pro_Lite_OS_22h2_by_%2540TheRimvydop.rar/file"
252 | }
253 | ]
254 | },
255 | {
256 | "name": "Windows 10 Pro 21H1 LiteOS",
257 | "description": "No description",
258 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230505-125152_orig.jpg",
259 | "downloads": [
260 | {
261 | "label": "Download (Mediafire)",
262 | "url": "https://www.mediafire.com/file/lk8cpdl0tdj4spf/Windows_10_Pro_32bit_21H1_APRIL_2021_-_LiteOS_by_%2540TheRimvydop.rar/file"
263 | }
264 | ]
265 | },
266 | {
267 | "name": "Windows 10 Enterprise LTSC Original",
268 | "description": "No description",
269 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230515-083924_orig.jpg",
270 | "downloads": [
271 | {
272 | "label": "Download (Mediafire)",
273 | "url": "https://www.mediafire.com/file/zt5n91oo7ql7373/Windows_10_Enterprise_LTSC_Original_by_%2540TheRimvydop.rar/file"
274 | }
275 | ]
276 | },
277 | {
278 | "name": "Windows 10 Enterprise 1903 Original",
279 | "description": "No description",
280 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230521-130320_orig.jpg",
281 | "downloads": [
282 | {
283 | "label": "Download (Google Drive)",
284 | "url": "https://drive.google.com/file/d/1kFNfUDIUuePO35I0PvMO-wjK938JoBQF/view?usp=drivesdk"
285 | }
286 | ]
287 | },
288 | {
289 | "name": "Windows 11 Pro 21H1 (Nexus LiteOS 22000.466)",
290 | "description": "No description",
291 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230524-132943_orig.jpg",
292 | "downloads": [
293 | {
294 | "label": "Download (Google Drive)",
295 | "url": "https://drive.google.com/file/d/117MGraRGrIWBZqIlfSjJIUxpvJcsirLI/view?usp=drivesdk"
296 | }
297 | ]
298 | },
299 | {
300 | "name": "Windows 10 Enterprise PE 1903",
301 | "description": "No description",
302 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230528-124414_orig.jpg",
303 | "downloads": [
304 | {
305 | "label": "Download (Google Drive)",
306 | "url": "https://drive.google.com/file/d/1_y2IrXY6GIpdkKjBty6GJ0xqRh0jTII6/view?usp=drivesdk"
307 | }
308 | ]
309 | },
310 | {
311 | "name": "Windows 10 Enterprise LTSC (Tiny 10)",
312 | "description": "No description",
313 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230530-085250_orig.jpg",
314 | "downloads": [
315 | {
316 | "label": "Download (Mediafire)",
317 | "url": "https://www.mediafire.com/file/tpy1bhsdeh6l607/Tiny_10.rar/file"
318 | }
319 | ]
320 | },
321 | {
322 | "name": "Windows 11 Pro LTSC 22H2 LiteOS",
323 | "description": "No description",
324 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230530-085646_orig.jpg",
325 | "downloads": [
326 | {
327 | "label": "Download (Mediafire)",
328 | "url": "https://www.mediafire.com/file/lfzaalvx4cb2k33/Windows+LTSC+2H22+LiteOS+by+@TheRimvydop.rar/file"
329 | }
330 | ]
331 | },
332 | {
333 | "name": "Windows 10 Pro 22H2 (Nexus LiteOS)",
334 | "description": "No description",
335 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230605-102633_orig.jpg",
336 | "downloads": [
337 | {
338 | "label": "Download (Mediafire)",
339 | "url": "https://www.mediafire.com/file/z8cqoz42sq0lf4e/Nexus_LiteOS_10_Pro_22H2_by_%2540Rimvydop.rar/file"
340 | }
341 | ]
342 | },
343 | {
344 | "name": "Windows 10 Pro 21H1 (Phoenix LiteOS)",
345 | "description": "No description",
346 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230617-112845_orig.jpg",
347 | "downloads": [
348 | {
349 | "label": "Download (Mediafire)",
350 | "url": "https://www.mediafire.com/file/cclcqpaupn3xfgv/Windows_10_Pro_%2528Phoenix_LiteOS_Gamer%2529_by_%2540TheRimvydop.rar/file"
351 | }
352 | ]
353 | },
354 | {
355 | "name": "Windows Server 2016",
356 | "description": "No description",
357 | "iconUrl": "https://winlayassets.a3x.xyz/images/img.icons8.png",
358 | "downloads": [
359 | {
360 | "label": "Download (Google Drive)",
361 | "url": "https://drive.google.com/file/d/1-3siTuqBCVo7Qqjsyl1nUYRVOOGAhYrF/view?usp=drivesdk"
362 | }
363 | ]
364 | },
365 | {
366 | "name": "Windows 10 Enterprise Evaluation",
367 | "description": "No description",
368 | "iconUrl": "https://winlayassets.a3x.xyz/images/img.icons8.png",
369 | "downloads": [
370 | {
371 | "label": "Download (Google Drive)",
372 | "url": "https://drive.google.com/file/d/147d6Sp-7HvBXChh2SNFq2z5dBdH7euNT/view?usp=drivesdk"
373 | }
374 | ]
375 | },
376 | {
377 | "name": "Windows Server 2022 Datacenter",
378 | "description": "No description",
379 | "iconUrl": "https://winlayassets.a3x.xyz/images/img.icons8.png",
380 | "downloads": [
381 | {
382 | "label": "Download (Mediafire)",
383 | "url": "https://www.mediafire.com/file/l2ckgs12uge012c/Windows_Server_2025.rar/file?dkey=e7rmnpj7cgr&r=344"
384 | }
385 | ]
386 | },
387 | {
388 | "name": "Windows XP SP3",
389 | "description": "No description",
390 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230525-153607_orig.jpg",
391 | "downloads": [
392 | {
393 | "label": "Download (Google Drive)",
394 | "url": "https://drive.google.com/file/d/157TL4N20irPwihDYzKmm_D_Jtt3Vfv4j/view?usp=drivesdk"
395 | }
396 | ]
397 | },
398 | {
399 | "name": "Windows Vista Ultimate",
400 | "description": "No description",
401 | "iconUrl": "https://winlayassets.a3x.xyz/images/20230525-153752_orig.jpg",
402 | "downloads": [
403 | {
404 | "label": "Download (Google Drive)",
405 | "url": "https://drive.google.com/file/d/14yRWNdDd-v6JSFGK5jbT0GJiJtanRp8t/view?usp=drivesdk"
406 | }
407 | ]
408 | },
409 | {
410 | "name": "Windows Longhorn 5048",
411 | "description": "No description",
412 | "iconUrl": "https://winlayassets.a3x.xyz/images/20231026-083704_orig.jpg",
413 | "downloads": [
414 | {
415 | "label": "Download (Pixeldrain)",
416 | "url": "https://pixeldrain.com/u/hbN5DwPw"
417 | }
418 | ]
419 | }
420 | ]
421 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winlay/a3x/SettingsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.winlay.a3x
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material3.*
6 | import androidx.compose.runtime.*
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.platform.LocalUriHandler
9 | import androidx.compose.ui.unit.dp
10 | import androidx.lifecycle.viewmodel.compose.viewModel
11 | import android.util.Base64
12 | import com.winlay.a3x.components.PremiumCard
13 | import com.winlay.a3x.ui.theme.LocalSpacing
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.Business
16 | import androidx.compose.material.icons.filled.Error
17 | import androidx.compose.material.icons.filled.Favorite
18 | import androidx.compose.material.icons.filled.Info
19 | import androidx.compose.material.icons.filled.Palette
20 |
21 | @Composable
22 | fun SettingsScreen(viewModel: ThemeViewModel = viewModel()) {
23 | val currentTheme by viewModel.theme.collectAsState()
24 | var showThemeDialog by remember { mutableStateOf(false) }
25 | var showAboutDialog by remember { mutableStateOf(false) }
26 | var showWhatsNewDialog by remember { mutableStateOf(false) }
27 | val uriHandler = LocalUriHandler.current
28 | var versionTapCount by remember { mutableStateOf(0) }
29 | val spacing = LocalSpacing.current
30 |
31 | Column(
32 | modifier = Modifier
33 | .fillMaxSize()
34 | .padding(spacing.medium),
35 | verticalArrangement = Arrangement.spacedBy(spacing.medium)
36 | ) {
37 | Text(
38 | "Settings",
39 | style = MaterialTheme.typography.headlineLarge,
40 | color = MaterialTheme.colorScheme.onSurface
41 | )
42 |
43 | PremiumCard(
44 | modifier = Modifier.fillMaxWidth()
45 | ) {
46 | Column(
47 | modifier = Modifier
48 | .fillMaxWidth()
49 | .padding(spacing.medium)
50 | ) {
51 | Text(
52 | "Appearance",
53 | style = MaterialTheme.typography.titleLarge,
54 | color = MaterialTheme.colorScheme.primary,
55 | modifier = Modifier.padding(bottom = spacing.small)
56 | )
57 |
58 | ListItem(
59 | headlineContent = {
60 | Text(
61 | "Theme",
62 | style = MaterialTheme.typography.bodyLarge
63 | )
64 | },
65 | supportingContent = {
66 | Text(
67 | currentTheme.displayName,
68 | style = MaterialTheme.typography.bodyMedium,
69 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
70 | )
71 | },
72 | modifier = Modifier
73 | .fillMaxWidth()
74 | .clickable { showThemeDialog = true },
75 | leadingContent = {
76 | Icon(
77 | imageVector = Icons.Default.Palette,
78 | contentDescription = "Theme",
79 | tint = MaterialTheme.colorScheme.primary
80 | )
81 | }
82 | )
83 | }
84 | }
85 |
86 | PremiumCard(
87 | modifier = Modifier.fillMaxWidth()
88 | ) {
89 | Column(
90 | modifier = Modifier
91 | .fillMaxWidth()
92 | .padding(spacing.medium)
93 | ) {
94 | Text(
95 | "About & Information",
96 | style = MaterialTheme.typography.titleLarge,
97 | color = MaterialTheme.colorScheme.primary,
98 | modifier = Modifier.padding(bottom = spacing.small)
99 | )
100 |
101 | ListItem(
102 | headlineContent = {
103 | Text(
104 | "What's New",
105 | style = MaterialTheme.typography.bodyLarge
106 | )
107 | },
108 | supportingContent = {
109 | Text(
110 | "Latest changes and updates",
111 | style = MaterialTheme.typography.bodyMedium,
112 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
113 | )
114 | },
115 | modifier = Modifier
116 | .fillMaxWidth()
117 | .clickable { showWhatsNewDialog = true },
118 | leadingContent = {
119 | Icon(
120 | imageVector = Icons.Default.Info,
121 | contentDescription = "What's New",
122 | tint = MaterialTheme.colorScheme.primary
123 | )
124 | }
125 | )
126 |
127 | ListItem(
128 | headlineContent = {
129 | Text(
130 | "About Winlay",
131 | style = MaterialTheme.typography.bodyLarge
132 | )
133 | },
134 | supportingContent = {
135 | Text(
136 | "App info and credits",
137 | style = MaterialTheme.typography.bodyMedium,
138 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
139 | )
140 | },
141 | modifier = Modifier
142 | .fillMaxWidth()
143 | .clickable { showAboutDialog = true },
144 | leadingContent = {
145 | Icon(
146 | imageVector = Icons.Default.Business,
147 | contentDescription = "About",
148 | tint = MaterialTheme.colorScheme.primary
149 | )
150 | }
151 | )
152 | }
153 | }
154 |
155 | PremiumCard(
156 | modifier = Modifier.fillMaxWidth()
157 | ) {
158 | Column(
159 | modifier = Modifier
160 | .fillMaxWidth()
161 | .padding(spacing.medium)
162 | ) {
163 | Text(
164 | "Support & Contribution",
165 | style = MaterialTheme.typography.titleLarge,
166 | color = MaterialTheme.colorScheme.primary,
167 | modifier = Modifier.padding(bottom = spacing.small)
168 | )
169 |
170 | ListItem(
171 | headlineContent = {
172 | Text(
173 | "Donate",
174 | style = MaterialTheme.typography.bodyLarge
175 | )
176 | },
177 | supportingContent = {
178 | Text(
179 | "Support A3X development",
180 | style = MaterialTheme.typography.bodyMedium,
181 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
182 | )
183 | },
184 | modifier = Modifier
185 | .fillMaxWidth()
186 | .clickable {
187 | uriHandler.openUri("https://a3x.xyz/donate")
188 | },
189 | leadingContent = {
190 | Icon(
191 | imageVector = Icons.Default.Favorite,
192 | contentDescription = "Donate",
193 | tint = MaterialTheme.colorScheme.primary
194 | )
195 | }
196 | )
197 | }
198 | }
199 | }
200 |
201 | if (showThemeDialog) {
202 | AlertDialog(
203 | onDismissRequest = { showThemeDialog = false },
204 | title = {
205 | Text(
206 | "Choose Theme",
207 | style = MaterialTheme.typography.titleLarge
208 | )
209 | },
210 | text = {
211 | Column {
212 | ThemeOption.values().forEach { option ->
213 | Row(
214 | modifier = Modifier
215 | .fillMaxWidth()
216 | .clickable {
217 | viewModel.setTheme(option)
218 | showThemeDialog = false
219 | }
220 | .padding(vertical = spacing.small),
221 | horizontalArrangement = Arrangement.Start
222 | ) {
223 | RadioButton(
224 | selected = currentTheme == option,
225 | onClick = {
226 | viewModel.setTheme(option)
227 | showThemeDialog = false
228 | },
229 | colors = RadioButtonDefaults.colors(
230 | selectedColor = MaterialTheme.colorScheme.primary
231 | )
232 | )
233 | Spacer(Modifier.width(spacing.small))
234 | Text(
235 | option.displayName,
236 | style = MaterialTheme.typography.bodyLarge
237 | )
238 | }
239 | }
240 | }
241 | },
242 | confirmButton = {
243 | TextButton(
244 | onClick = { showThemeDialog = false },
245 | colors = ButtonDefaults.textButtonColors(
246 | contentColor = MaterialTheme.colorScheme.primary
247 | )
248 | ) {
249 | Text("Cancel")
250 | }
251 | },
252 | containerColor = MaterialTheme.colorScheme.surface,
253 | titleContentColor = MaterialTheme.colorScheme.onSurface,
254 | textContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
255 | )
256 | }
257 |
258 | if (showAboutDialog) {
259 | AlertDialog(
260 | onDismissRequest = {
261 | showAboutDialog = false
262 | versionTapCount = 0
263 | },
264 | title = { Text("About Winlay") },
265 | text = {
266 | Column {
267 | Text("App name:", style = MaterialTheme.typography.labelMedium)
268 | Text("Winlay", style = MaterialTheme.typography.bodyMedium)
269 | Spacer(modifier = Modifier.height(8.dp))
270 |
271 | Text("Version:", style = MaterialTheme.typography.labelMedium)
272 | Text(
273 | "v2.0",
274 | style = MaterialTheme.typography.bodyMedium,
275 | modifier = Modifier.clickable {
276 | versionTapCount++
277 | if (versionTapCount >= 8) {
278 | try {
279 | val rickrollUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
280 | uriHandler.openUri(rickrollUrl)
281 | } catch (e: Exception) {
282 | e.printStackTrace()
283 | } finally {
284 | versionTapCount = 0
285 | }
286 | }
287 | }
288 | )
289 | Spacer(modifier = Modifier.height(8.dp))
290 |
291 | Text("Developer by:", style = MaterialTheme.typography.labelMedium)
292 | Text("A3X", style = MaterialTheme.typography.bodyMedium)
293 | Spacer(modifier = Modifier.height(8.dp))
294 |
295 | Text("A3X Website:", style = MaterialTheme.typography.labelMedium)
296 | Text(
297 | text = "https://a3x.xyz/",
298 | color = MaterialTheme.colorScheme.primary,
299 | style = MaterialTheme.typography.bodyMedium,
300 | modifier = Modifier.clickable {
301 | uriHandler.openUri("https://a3x.xyz/")
302 | }
303 | )
304 | Spacer(modifier = Modifier.height(8.dp))
305 |
306 | Text("Winlay Website:", style = MaterialTheme.typography.labelMedium)
307 | Text(
308 | text = "https://winlayapp.weebly.com/",
309 | color = MaterialTheme.colorScheme.primary,
310 | style = MaterialTheme.typography.bodyMedium,
311 | modifier = Modifier.clickable {
312 | uriHandler.openUri("https://winlayapp.weebly.com/")
313 | }
314 | )
315 | Spacer(modifier = Modifier.height(8.dp))
316 |
317 | Text("Github:", style = MaterialTheme.typography.labelMedium)
318 | Text(
319 | text = "https://github.com/a3x-xyz/Winlay",
320 | color = MaterialTheme.colorScheme.primary,
321 | style = MaterialTheme.typography.bodyMedium,
322 | modifier = Modifier.clickable {
323 | uriHandler.openUri("https://github.com/a3x-xyz/Winlay")
324 | }
325 | )
326 | Spacer(modifier = Modifier.height(8.dp))
327 |
328 | Text("License:", style = MaterialTheme.typography.labelMedium)
329 | Text(
330 | text = "GNU General Public License v3.0",
331 | color = MaterialTheme.colorScheme.primary,
332 | style = MaterialTheme.typography.bodyMedium,
333 | modifier = Modifier.clickable {
334 | uriHandler.openUri("https://www.gnu.org/licenses/gpl-3.0.html")
335 | }
336 | )
337 | Spacer(modifier = Modifier.height(8.dp))
338 | }
339 | },
340 | confirmButton = {
341 | TextButton(onClick = {
342 | showAboutDialog = false
343 | versionTapCount = 0
344 | }) {
345 | Text("OK")
346 | }
347 | }
348 | )
349 | }
350 |
351 | if (showWhatsNewDialog) {
352 | AlertDialog(
353 | onDismissRequest = { showWhatsNewDialog = false },
354 | title = { Text("What's new in v2.0") },
355 | text = {
356 | Column {
357 | Text("• Redesigned UI", style = MaterialTheme.typography.bodyMedium)
358 | Text("• Updated icons", style = MaterialTheme.typography.bodyMedium)
359 | Text("• Version numbers now start with 'v'", style = MaterialTheme.typography.bodyMedium)
360 | Text("• Bug fixes", style = MaterialTheme.typography.bodyMedium)
361 | Text("• Changed JSON and image URLs to avoid rate limits.", style = MaterialTheme.typography.bodyMedium)
362 | }
363 | },
364 | confirmButton = {
365 | TextButton(onClick = { showWhatsNewDialog = false }) {
366 | Text("OK")
367 | }
368 | }
369 | )
370 | }
371 | }
372 |
--------------------------------------------------------------------------------