├── themmo
├── .gitignore
├── src
│ ├── commonMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── themmo
│ │ │ └── core
│ │ │ ├── MonetMode.kt
│ │ │ └── ThemingMode.kt
│ ├── wasmJsMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── themmo
│ │ │ └── ProvideColorScheme.wasmJs.kt
│ └── androidMain
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── themmo
│ │ └── ProvideColorScheme.android.kt
└── build.gradle.kts
├── core
├── common
│ ├── .gitignore
│ ├── src
│ │ ├── commonMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── common
│ │ │ │ ├── PathUtils.kt
│ │ │ │ ├── Const.kt
│ │ │ │ ├── ColorUtils.kt
│ │ │ │ └── Serializer.kt
│ │ ├── wasmJsMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── common
│ │ │ │ ├── NotReady.kt
│ │ │ │ ├── PathUtils.wasmJs.kt
│ │ │ │ └── FlowUtils.wasmJs.kt
│ │ ├── androidMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── common
│ │ │ │ ├── PathUtils.android.kt
│ │ │ │ ├── PlatformFileUtils.kt
│ │ │ │ ├── FlowUtils.android.kt
│ │ │ │ └── FileUtils.kt
│ │ └── commonTest
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── common
│ │ │ └── ColorUtilsTest.kt
│ └── build.gradle.kts
├── data
│ ├── .gitignore
│ ├── testFiles
│ │ └── file.png
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── data
│ │ │ ├── InstalledAppsProvider.kt
│ │ │ ├── WidgetDataPresetCustomRepository.kt
│ │ │ ├── WidgetDataRepository.kt
│ │ │ ├── IconPackCustomRepository.kt
│ │ │ └── LayerContextImpl.kt
│ │ ├── androidMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── data
│ │ │ ├── InstalledAppsProviderImpl.kt
│ │ │ └── BatteryInfoProviderImpl.kt
│ │ ├── androidUnitTest
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── data
│ │ │ └── DynamicColorSchemeProviderImplTest.kt
│ │ └── androidInstrumentedTest
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── sukko
│ │ └── core
│ │ └── data
│ │ └── WidgetDataRepositoryDeleteTest.kt
├── model
│ ├── .gitignore
│ ├── src
│ │ ├── androidMain
│ │ │ └── assets
│ │ │ │ └── widgetDataPreset
│ │ │ │ ├── -1
│ │ │ │ └── preview.png
│ │ │ │ └── -2
│ │ │ │ └── preview.png
│ │ ├── commonMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── model
│ │ │ │ ├── WidgetSubscriptionInfo.kt
│ │ │ │ ├── InstalledApp.kt
│ │ │ │ ├── basic
│ │ │ │ └── MediaInfo.kt
│ │ │ │ ├── data
│ │ │ │ ├── BatteryInfoProvider.kt
│ │ │ │ ├── MediaInfoProvider.kt
│ │ │ │ └── DynamicColorSchemeProvider.kt
│ │ │ │ ├── ImportingIconPack.kt
│ │ │ │ └── modifier
│ │ │ │ ├── ClipModifier.kt
│ │ │ │ ├── SizeModifier.kt
│ │ │ │ ├── AlphaModifier.kt
│ │ │ │ ├── OffsetModifier.kt
│ │ │ │ ├── WidthModifier.kt
│ │ │ │ ├── FillMaxSizeModifier.kt
│ │ │ │ ├── FillMaxWidthModifier.kt
│ │ │ │ ├── HeightModifier.kt
│ │ │ │ ├── FillMaxHeightModifier.kt
│ │ │ │ ├── BoxAlignmentModifier.kt
│ │ │ │ ├── RowAlignmentModifier.kt
│ │ │ │ └── ColumnAlignmentModifier.kt
│ │ └── commonTest
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── model
│ │ │ └── WidgetDataUpdateLayerTest.kt
│ └── build.gradle.kts
├── remote
│ ├── .gitignore
│ ├── src
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── remote
│ │ │ └── RemoteClient.kt
│ └── build.gradle.kts
├── routes
│ ├── .gitignore
│ ├── src
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── routes
│ │ │ └── NavigationResult.kt
│ └── build.gradle.kts
├── ui
│ ├── .gitignore
│ ├── src
│ │ ├── commonMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── ui
│ │ │ │ ├── CommonBackHandler.kt
│ │ │ │ ├── EmptyScreen.kt
│ │ │ │ ├── RemoveButton.kt
│ │ │ │ ├── NavigateUpButton.kt
│ │ │ │ ├── ErrorScreenPlaceholder.kt
│ │ │ │ └── ListHeader.kt
│ │ ├── wasmJsMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── ui
│ │ │ │ └── CommonBackHandler.wasmJs.kt
│ │ └── androidMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── ui
│ │ │ └── CommonBackHandler.android.kt
│ └── build.gradle.kts
├── widget
│ ├── .gitignore
│ ├── src
│ │ ├── androidMain
│ │ │ ├── res
│ │ │ │ ├── drawable
│ │ │ │ │ └── rounded_corner.xml
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── colors.xml
│ │ │ │ ├── layout
│ │ │ │ │ ├── render_host_clickable_area_layout.xml
│ │ │ │ │ ├── render_host_main_layout.xml
│ │ │ │ │ ├── render_host_clickable_layout.xml
│ │ │ │ │ ├── render_host_image_layout.xml
│ │ │ │ │ ├── error_layout.xml
│ │ │ │ │ └── default_initial_layout.xml
│ │ │ │ ├── values-night
│ │ │ │ │ └── colors.xml
│ │ │ │ ├── values-night-v34
│ │ │ │ │ └── colors.xml
│ │ │ │ └── values-v34
│ │ │ │ │ └── colors.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── widget
│ │ │ │ ├── PowerStateReceiver.kt
│ │ │ │ └── WidgetInfoRepositoryImpl.kt
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── widget
│ │ │ └── WidgetInfoRepository.kt
│ └── build.gradle.kts
├── database
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── sukko
│ │ └── core
│ │ └── database
│ │ ├── SukkoDatabase.kt
│ │ ├── IconPackBased.kt
│ │ ├── WidgetDataBased.kt
│ │ └── WidgetDataPresetBased.kt
├── fontfiles
│ ├── .gitignore
│ ├── src
│ │ ├── commonMain
│ │ │ └── kotlin
│ │ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── fontfiles
│ │ │ │ ├── FontFamilyLoader.kt
│ │ │ │ └── FontFileCustomRepository.kt
│ │ └── wasmJsMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── fontfiles
│ │ │ └── FontFamilyLoader.wasmJs.kt
│ └── build.gradle.kts
├── unglance
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── androidx
│ │ │ └── glance
│ │ │ └── README.md
│ └── build.gradle.kts
├── designsystem
│ ├── .gitignore
│ ├── src
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── designsystem
│ │ │ ├── theme
│ │ │ ├── ListArrangement.kt
│ │ │ └── Sizes.kt
│ │ │ ├── LocalFilesDirPath.kt
│ │ │ ├── LocalImageLoader.kt
│ │ │ └── Preview2.kt
│ └── build.gradle.kts
├── importexport
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── importexport
│ │ │ └── DataMigration.kt
│ └── build.gradle.kts
├── medialistener
│ ├── .gitignore
│ ├── src
│ │ └── androidMain
│ │ │ ├── res
│ │ │ └── values
│ │ │ │ └── string.xml
│ │ │ ├── kotlin
│ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── sadellie
│ │ │ │ └── sukko
│ │ │ │ └── core
│ │ │ │ └── medialistener
│ │ │ │ ├── MediaListener.kt
│ │ │ │ ├── BitmapUtils.kt
│ │ │ │ └── MediaListenerService.kt
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── script
│ ├── .gitignore
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── script
│ │ │ ├── simplify
│ │ │ ├── SimplificationStep.kt
│ │ │ ├── SimplificationType.kt
│ │ │ ├── SimplifyOrBoolean.kt
│ │ │ ├── SimplifyAndBoolean.kt
│ │ │ ├── SimplifyDivideNumbers.kt
│ │ │ ├── SimplificationRule.kt
│ │ │ ├── SimplifyMultiplyNumbers.kt
│ │ │ └── SimplifyPlusNumbers.kt
│ │ │ ├── docs
│ │ │ ├── DocsRepository.kt
│ │ │ └── Docs.kt
│ │ │ ├── FunctionNode.kt
│ │ │ ├── BracketsNode.kt
│ │ │ ├── ScriptContext.kt
│ │ │ └── ASTNode.kt
│ │ ├── wasmJsMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── script
│ │ │ └── docs
│ │ │ └── DocsRepository.wasmJs.kt
│ │ ├── androidUnitTest
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── core
│ │ │ └── script
│ │ │ └── docs
│ │ │ └── DocsRepositoryImplTest.kt
│ │ └── commonTest
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── sukko
│ │ └── core
│ │ └── script
│ │ ├── simplify
│ │ ├── SimplifyMultiplyNumbersTest.kt
│ │ ├── SimplifyPlusNumbersTest.kt
│ │ ├── SimplifyDivideNumbersTest.kt
│ │ ├── SimplifyOrBooleanTest.kt
│ │ ├── SimplifyAndBooleanTest.kt
│ │ ├── equality
│ │ │ ├── SimplifyEqualCheckTest.kt
│ │ │ ├── SimplifyNotEqualCheckTest.kt
│ │ │ ├── SimplifyLessCheckTest.kt
│ │ │ ├── SimplifyGreaterCheckTest.kt
│ │ │ ├── SimplifyLessOrEqualCheckTest.kt
│ │ │ └── SimplifyGreaterOrEqualCheckTest.kt
│ │ └── DoNotVisitIfBlocksTest.kt
│ │ └── ToFormattedStringTest.kt
└── iconfiles
│ ├── .gitignore
│ └── src
│ └── commonMain
│ └── kotlin
│ └── io
│ └── github
│ └── sadellie
│ └── sukko
│ └── core
│ └── iconfiles
│ └── IconFile.kt
├── feature
├── editor
│ ├── .gitignore
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── feature
│ │ │ └── editor
│ │ │ ├── selector
│ │ │ ├── scripteditor
│ │ │ │ ├── ScriptEditorPage.kt
│ │ │ │ └── TextFieldStateUtils.kt
│ │ │ ├── AppSelectorSheet.kt
│ │ │ └── Utils.kt
│ │ │ └── modifiers
│ │ │ ├── EditorModifierClip.kt
│ │ │ ├── EditorModifierSize.kt
│ │ │ ├── EditorModifierWidth.kt
│ │ │ ├── EditorModifierBoxAlignment.kt
│ │ │ ├── EditorModifierHeight.kt
│ │ │ ├── EditorModifierRowAlignment.kt
│ │ │ ├── EditorModifierColumnAlignment.kt
│ │ │ ├── EditorModifierAlpha.kt
│ │ │ ├── EditorModifierPaddingAllSides.kt
│ │ │ ├── EditorModifierFillMaxWidth.kt
│ │ │ ├── EditorModifierFillMaxHeight.kt
│ │ │ └── EditorModifierFillMaxSize.kt
│ │ ├── wasmJsMain
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── feature
│ │ │ └── editor
│ │ │ ├── selector
│ │ │ ├── AppSelectorSheet.wasmJs.kt
│ │ │ └── scripteditor
│ │ │ │ └── InputPage.wasmJs.kt
│ │ │ ├── parameters
│ │ │ └── EditorParametersImageLayer.wasmJs.kt
│ │ │ └── EditorScene.wasmJs.kt
│ │ └── androidMain
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── sukko
│ │ └── feature
│ │ └── editor
│ │ └── selector
│ │ └── scripteditor
│ │ └── InputPage.android.kt
├── home
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── feature
│ │ │ └── home
│ │ │ └── HomeRoute.kt
│ └── build.gradle.kts
├── settings
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── feature
│ │ │ └── settings
│ │ │ ├── SettingsRoute.kt
│ │ │ ├── notificationlistener
│ │ │ └── NotificationListenerRoute.kt
│ │ │ └── Utils.kt
│ └── build.gradle.kts
├── fontseditor
│ ├── .gitignore
│ └── build.gradle.kts
├── icopackeditor
│ ├── .gitignore
│ └── build.gradle.kts
├── importpreset
│ ├── .gitignore
│ └── build.gradle.kts
├── saveaspreset
│ ├── .gitignore
│ └── build.gradle.kts
└── presetselector
│ ├── .gitignore
│ └── build.gradle.kts
├── docs
├── banner.png
├── phone1.png
├── phone2.png
├── phone3.png
├── phone4.png
├── banner-no-bg.png
├── index.md
├── stylesheets
│ └── extra.css
├── privacy.md
├── logo.svg
├── logo2.svg
└── press.md
├── app
├── src
│ ├── wasmJsMain
│ │ ├── resources
│ │ │ ├── externals.js
│ │ │ ├── styles.css
│ │ │ └── index.html
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ ├── Module.wasmJs.kt
│ │ │ └── main.kt
│ ├── androidMain
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── values-night
│ │ │ │ └── colors.xml
│ │ │ ├── values-v34
│ │ │ │ └── colors.xml
│ │ │ ├── values-night-v34
│ │ │ │ └── colors.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ └── widget_info.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_foreground.xml
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── sadellie
│ │ │ └── sukko
│ │ │ └── MainActivity.kt
│ └── commonMain
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── sadellie
│ │ └── sukko
│ │ └── Module.kt
└── proguard-rules.pro
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── material-symbols
├── src
│ └── commonMain
│ │ └── kotlin
│ │ └── google
│ │ └── material
│ │ └── design
│ │ └── symbols
│ │ ├── Symbols.kt
│ │ ├── LineEnd.kt
│ │ └── Check.kt
└── build.gradle.kts
├── .gitignore
├── licence
└── README.md
├── detekt
└── baseline.xml
├── gradle.properties
├── mkdocs.yml
├── .github
└── workflows
│ └── ci.yml
├── BUILD.md
└── settings.gradle.kts
/themmo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/model/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/remote/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/routes/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/widget/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/database/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/fontfiles/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/unglance/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/editor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/settings/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/designsystem/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/importexport/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/medialistener/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/fontseditor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/icopackeditor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/importpreset/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/saveaspreset/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/presetselector/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/script/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | **/files/scripting.json
--------------------------------------------------------------------------------
/core/iconfiles/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /src/androidMain/assets/iconPacks
--------------------------------------------------------------------------------
/docs/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/banner.png
--------------------------------------------------------------------------------
/docs/phone1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/phone1.png
--------------------------------------------------------------------------------
/docs/phone2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/phone2.png
--------------------------------------------------------------------------------
/docs/phone3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/phone3.png
--------------------------------------------------------------------------------
/docs/phone4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/phone4.png
--------------------------------------------------------------------------------
/docs/banner-no-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/docs/banner-no-bg.png
--------------------------------------------------------------------------------
/core/data/testFiles/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/core/data/testFiles/file.png
--------------------------------------------------------------------------------
/app/src/wasmJsMain/resources/externals.js:
--------------------------------------------------------------------------------
1 | function callLogs() {
2 | console.log("this is a test");
3 | }
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/core/unglance/src/main/kotlin/androidx/glance/README.md:
--------------------------------------------------------------------------------
1 | Modifier source code of Compose Glance (part of Android Jetpack). Licence included.
--------------------------------------------------------------------------------
/material-symbols/src/commonMain/kotlin/google/material/design/symbols/Symbols.kt:
--------------------------------------------------------------------------------
1 | package google.material.design.symbols
2 |
3 | object Symbols
4 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/wasmJsMain/resources/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | overflow: hidden;
7 | }
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/androidMain/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFD8EC
4 |
--------------------------------------------------------------------------------
/core/model/src/androidMain/assets/widgetDataPreset/-1/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/core/model/src/androidMain/assets/widgetDataPreset/-1/preview.png
--------------------------------------------------------------------------------
/core/model/src/androidMain/assets/widgetDataPreset/-2/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadellie/sukko/HEAD/core/model/src/androidMain/assets/widgetDataPreset/-2/preview.png
--------------------------------------------------------------------------------
/app/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sukko
4 |
5 |
--------------------------------------------------------------------------------
/core/common/src/commonMain/kotlin/io/github/sadellie/sukko/core/common/PathUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import okio.Path
4 |
5 | expect val ASSET_PATH: Path
6 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/common/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/common/NotReady.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | val notReady: Nothing
4 | get() = error("WASM is not ready yet")
5 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Visit [Press kit](./press.md) to learn more about world's first open source widget creator.
6 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/drawable/rounded_corner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/common/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/common/PathUtils.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import okio.Path
4 |
5 | actual val ASSET_PATH: Path
6 | get() = notReady
7 |
--------------------------------------------------------------------------------
/core/medialistener/src/androidMain/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Access currently playing media and notifications when needed
4 |
5 |
--------------------------------------------------------------------------------
/core/common/src/androidMain/kotlin/io/github/sadellie/sukko/core/common/PathUtils.android.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import okio.Path.Companion.toPath
4 |
5 | actual val ASSET_PATH by lazy { "file:///android_asset/".toPath() }
6 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/CommonBackHandler.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable expect fun BackHandler(enabled: Boolean, block: () -> Unit)
6 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Error
4 | Configure this widget in the app
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | local.properties
4 | /.idea
5 | .DS_Store
6 | /build/
7 | /material-symbols/build/
8 | /build-logic/convention/build
9 | *.lock
10 | /app/build
11 | /app/*.log
12 | .kotlin
13 | /internals
14 | /fake/public
15 | /detekt/report.*
--------------------------------------------------------------------------------
/app/src/androidMain/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/background_dark
4 | @android:color/system_accent1_600
5 |
6 |
--------------------------------------------------------------------------------
/core/ui/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/ui/CommonBackHandler.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable actual fun BackHandler(enabled: Boolean, block: () -> Unit) {}
6 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/WidgetSubscriptionInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model
2 |
3 | data class WidgetSubscriptionInfo(
4 | val isTime: Boolean,
5 | val isBattery: Boolean,
6 | val isMedia: Boolean,
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/values-v34/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_background_light
4 | @android:color/system_on_background_light
5 |
6 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/InstalledApp.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model
2 |
3 | import androidx.compose.ui.graphics.ImageBitmap
4 |
5 | data class InstalledApp(val label: String, val packageId: String, val icon: ImageBitmap)
6 |
--------------------------------------------------------------------------------
/feature/home/src/main/kotlin/io/github/sadellie/sukko/feature/home/HomeRoute.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.home
2 |
3 | import androidx.navigation3.runtime.NavKey
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data object HomeRoute : NavKey
8 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/values-night-v34/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_background_dark
4 | @android:color/system_on_background_dark
5 |
6 |
--------------------------------------------------------------------------------
/core/routes/src/commonMain/kotlin/io/github/sadellie/sukko/core/routes/NavigationResult.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.routes
2 |
3 | sealed interface NavigationResult {
4 | data class PresetSelectorResult(val presetId: Long, val isBuiltIn: Boolean) : NavigationResult
5 | }
6 |
--------------------------------------------------------------------------------
/licence/README.md:
--------------------------------------------------------------------------------
1 | This folder contains licenses for:
2 |
3 | - shub39/Rush. MediaListener
4 | - AndroidX Glance. Modifier source code of Compose Glance (part of Android Jetpack)
5 | - Material Design Icons. Material Design icons by Google (Material Symbols)
6 | - LouisCAD/Splitties. SuspendLazy
--------------------------------------------------------------------------------
/core/data/src/commonMain/kotlin/io/github/sadellie/sukko/core/data/InstalledAppsProvider.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import io.github.sadellie.sukko.core.model.InstalledApp
4 |
5 | interface InstalledAppsProvider {
6 | suspend fun getAllApps(): List
7 | }
8 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/io/github/sadellie/sukko/feature/settings/SettingsRoute.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.settings
2 |
3 | import androidx.navigation3.runtime.NavKey
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable data object SettingsRoute : NavKey
7 |
--------------------------------------------------------------------------------
/core/routes/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("sukko.multiplatform.library") }
2 |
3 | kotlin {
4 | sourceSets.commonMain.dependencies {
5 | implementation(libs.androidx.navigation3.navigation3.runtime)
6 | }
7 | }
8 |
9 | android { namespace = "io.github.sadellie.sukko.core.routes" }
10 |
--------------------------------------------------------------------------------
/themmo/src/commonMain/kotlin/io/github/sadellie/themmo/core/MonetMode.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.themmo.core
2 |
3 | enum class MonetMode {
4 | TonalSpot,
5 | Neutral,
6 | Vibrant,
7 | Expressive,
8 | Rainbow,
9 | FruitSalad,
10 | Monochrome,
11 | Fidelity,
12 | Content,
13 | }
14 |
--------------------------------------------------------------------------------
/core/common/src/commonMain/kotlin/io/github/sadellie/sukko/core/common/Const.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | /** Schema of all saved data, both serializable and in tables. */
4 | const val SCHEMA_VERSION = 1
5 |
6 | /** Extension of exported presets */
7 | const val EXPORT_EXTENSION = "sukko"
8 |
--------------------------------------------------------------------------------
/core/designsystem/src/commonMain/kotlin/io/github/sadellie/sukko/core/designsystem/theme/ListArrangement.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.designsystem.theme
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.ui.unit.dp
5 |
6 | val ListArrangement = Arrangement.spacedBy(2.dp)
7 |
--------------------------------------------------------------------------------
/core/remote/src/commonMain/kotlin/io/github/sadellie/sukko/core/remote/RemoteClient.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.remote
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.cache.HttpCache
5 |
6 | class RemoteClient {
7 | val ktorClient = HttpClient { install(HttpCache.Companion) }
8 | }
9 |
--------------------------------------------------------------------------------
/material-symbols/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | kotlin { sourceSets.commonMain.dependencies { implementation(compose.ui) } }
8 |
9 | android { namespace = "google.material.design.symbols" }
10 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplificationStep.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 |
5 | internal data class SimplificationStep(
6 | val simplifiedASTNode: ASTNode,
7 | val type: SimplificationType,
8 | )
9 |
--------------------------------------------------------------------------------
/core/ui/src/androidMain/kotlin/io/github/sadellie/sukko/core/ui/CommonBackHandler.android.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun BackHandler(enabled: Boolean, block: () -> Unit) = BackHandler(enabled, block)
8 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/scripteditor/ScriptEditorPage.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector.scripteditor
2 |
3 | internal sealed interface ScriptEditorPage {
4 | data object InputPage : ScriptEditorPage
5 |
6 | data object DocsPage : ScriptEditorPage
7 | }
8 |
--------------------------------------------------------------------------------
/core/remote/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("sukko.multiplatform.library") }
2 |
3 | kotlin {
4 | sourceSets.commonMain.dependencies { implementation(libs.io.ktor.ktor.client.core) }
5 | sourceSets.androidMain.dependencies { implementation(libs.io.ktor.ktor.client.okhttp) }
6 | }
7 |
8 | android { namespace = "io.github.sadellie.sukko.core.remote" }
9 |
--------------------------------------------------------------------------------
/themmo/src/commonMain/kotlin/io/github/sadellie/themmo/core/ThemingMode.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.themmo.core
2 |
3 | enum class ThemingMode {
4 | /** Will ask system which theme to use. */
5 | AUTO,
6 |
7 | /** Will use light colors only. */
8 | FORCE_LIGHT,
9 |
10 | /** Will use dark colors only. */
11 | FORCE_DARK,
12 | }
13 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/io/github/sadellie/sukko/feature/settings/notificationlistener/NotificationListenerRoute.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.settings.notificationlistener
2 |
3 | import androidx.navigation3.runtime.NavKey
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable data object NotificationListenerRoute : NavKey
7 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/render_host_clickable_area_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/themmo/src/wasmJsMain/kotlin/io/github/sadellie/themmo/ProvideColorScheme.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.themmo
2 |
3 | import androidx.compose.material3.ColorScheme
4 | import androidx.compose.material3.darkColorScheme
5 | import androidx.compose.runtime.Composable
6 |
7 | @Composable actual fun provideDynamicColorScheme(isDark: Boolean): ColorScheme = darkColorScheme()
8 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/render_host_main_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/core/widget/src/commonMain/kotlin/io/github/sadellie/sukko/core/widget/WidgetInfoRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.widget
2 |
3 | import androidx.compose.ui.unit.DpSize
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface WidgetInfoRepository {
7 | fun allWidgetIds(): Flow
8 |
9 | suspend fun getWidgetSize(appWidgetId: Int): DpSize
10 | }
11 |
--------------------------------------------------------------------------------
/docs/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | :root > * {
2 | --md-primary-fg-color: #251E21;
3 | --md-typeset-color: #EDDFE4;
4 | --md-primary-bg-color: #EDDFE4;
5 | --md-default-bg-color: #251E21;
6 | --md-footer-bg-color--dark: #30282C;
7 | --md-default-fg-color--light: #EDDFE4;
8 | }
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #121318
4 | #E2E2E9
5 | #1E1F25
6 | #38393F
7 | #E2E2E9
8 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FAF8FF
4 | #1A1B20
5 | #EEEDF4
6 | #FAF8FF
7 | #1A1B20
8 |
9 |
--------------------------------------------------------------------------------
/core/script/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/script/docs/DocsRepository.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.docs
2 |
3 | actual class DocsRepositoryImpl actual constructor() : DocsRepository {
4 | actual override suspend fun load() {
5 | error("not ready")
6 | }
7 |
8 | actual override suspend fun search(query: String, lang: String): Docs? = error("not ready")
9 | }
10 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/EmptyScreen.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 |
8 | @Composable
9 | fun EmptyScreen() {
10 | Spacer(modifier = Modifier.fillMaxSize())
11 | }
12 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/render_host_clickable_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/render_host_image_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/core/designsystem/src/commonMain/kotlin/io/github/sadellie/sukko/core/designsystem/LocalFilesDirPath.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.designsystem
2 |
3 | import androidx.compose.runtime.ProvidableCompositionLocal
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import okio.Path
6 |
7 | val LocalFilesDirPath: ProvidableCompositionLocal = staticCompositionLocalOf {
8 | error("No local provided")
9 | }
10 |
--------------------------------------------------------------------------------
/core/designsystem/src/commonMain/kotlin/io/github/sadellie/sukko/core/designsystem/LocalImageLoader.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.designsystem
2 |
3 | import androidx.compose.runtime.ProvidableCompositionLocal
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import coil3.ImageLoader
6 |
7 | val LocalImageLoader: ProvidableCompositionLocal = staticCompositionLocalOf {
8 | error("No local provided")
9 | }
10 |
--------------------------------------------------------------------------------
/core/designsystem/src/commonMain/kotlin/io/github/sadellie/sukko/core/designsystem/theme/Sizes.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.designsystem.theme
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object Sizes {
6 | /** 28.dp */
7 | val extraLarge = 28.dp
8 | /** 16.dp */
9 | val large = 16.dp
10 | /** 14.dp */
11 | val medium = 14.dp
12 | /** 8.dp */
13 | val small = 8.dp
14 | /** 4.dp */
15 | val extraSmall = 4.dp
16 | }
17 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/docs/DocsRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.docs
2 |
3 | expect class DocsRepositoryImpl() : DocsRepository {
4 | override suspend fun load()
5 |
6 | override suspend fun search(query: String, lang: String): Docs?
7 | }
8 |
9 | interface DocsRepository {
10 | suspend fun load()
11 |
12 | suspend fun search(query: String, lang: String): Docs?
13 | }
14 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/AppSelectorSheet.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.composables.core.ModalBottomSheetState
5 |
6 | @Composable
7 | expect fun AppSelectorSheet(
8 | state: ModalBottomSheetState,
9 | onValueSelected: (label: String, packageName: String) -> Unit,
10 | packageName: String?,
11 | )
12 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/basic/MediaInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.basic
2 |
3 | data class MediaInfo(
4 | val lastUpdate: Long = 0L, // TODO remove?
5 | val artist: String?,
6 | val title: String?,
7 | val coverUri: String?,
8 | val playerPackageName: String,
9 | val playerIconUri: String?,
10 | val durationMs: Long,
11 | val positionMs: Long,
12 | val playbackState: Int,
13 | )
14 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplificationType.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | internal enum class SimplificationType {
4 | SUM_OF_NUMBERS,
5 | PRODUCT_OF_NUMBERS,
6 | DIVISION_OF_NUMBERS,
7 | LOGICAL_OR,
8 | LOGICAL_AND,
9 | EQUAL,
10 | NOT_EQUAL,
11 | GREATER,
12 | GREATER_OR_EQUAL,
13 | LESS,
14 | LESS_OR_EQUAL,
15 | EVAL_CONSTANT,
16 | EVAL_FUNCTION,
17 | }
18 |
--------------------------------------------------------------------------------
/core/fontfiles/src/commonMain/kotlin/io/github/sadellie/sukko/core/fontfiles/FontFamilyLoader.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.fontfiles
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.text.font.FontFamily
5 | import okio.Path
6 |
7 | expect class FontFamilyLoader() {
8 | suspend fun loadFromFontFile(fontFile: FontFile, fileDirPath: Path): FontFamily
9 |
10 | @Composable fun rememberFontFamily(fontFile: FontFile): FontFamily
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/wasmJsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sukko demo app
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/detekt/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LoopWithTooManyJumpStatements:ProcessRenderResult.kt$for
6 | ReturnCount:MediaListenerImpl.kt$MediaListenerImpl$private fun MediaMetadata.extractCoverUri(): String?
7 | ReturnCount:WidgetSubscriptionInfoGenerator.kt$private fun isMediaSubscriber(layer: Layer.Cold, allScriptTokens: Set<Token3>): Boolean
8 |
9 |
10 |
--------------------------------------------------------------------------------
/feature/editor/src/wasmJsMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/AppSelectorSheet.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.composables.core.ModalBottomSheetState
5 | import io.github.sadellie.sukko.core.common.notReady
6 |
7 | @Composable
8 | actual fun AppSelectorSheet(
9 | state: ModalBottomSheetState,
10 | onValueSelected: (label: String, packageName: String) -> Unit,
11 | packageName: String?,
12 | ) {
13 | notReady
14 | }
15 |
--------------------------------------------------------------------------------
/core/fontfiles/src/commonMain/kotlin/io/github/sadellie/sukko/core/fontfiles/FontFileCustomRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.fontfiles
2 |
3 | import io.github.vinceglb.filekit.PlatformFile
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface FontFileCustomRepository {
7 | fun loadAll(): Flow>
8 |
9 | suspend fun importFontFiles(filesToImport: List)
10 |
11 | suspend fun delete(fontFile: FontFile.Custom)
12 |
13 | suspend fun rename(fontFile: FontFile.Custom, newName: String)
14 | }
15 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/data/BatteryInfoProvider.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.data
2 |
3 | /** Provider for device battery and power information */
4 | interface BatteryInfoProvider {
5 | /** Current battery capacity. 0 to 100 inclusive */
6 | val capacity: Int
7 |
8 | /** Battery status. See docs or implementation for possible values */
9 | val status: String
10 |
11 | /** How may second until device charges to 100 or discharges to 0 */
12 | val chargeDischargeSeconds: Int
13 | }
14 |
--------------------------------------------------------------------------------
/docs/privacy.md:
--------------------------------------------------------------------------------
1 | # Privacy policy
2 |
3 | ## Application
4 | The application doesn't collect or share any user data on its own.
5 |
6 | ## Third party
7 | The application allows you to access internet resources that you specify. Those internet resources have their own privacy policy that you should verify.
8 |
9 | ## Exported data
10 | The application allows you to export and import user generated content. You are fully responsible for any information that it contains.
11 |
12 | ---
13 | Open an issue in the repository or email me if you have questions.
14 |
--------------------------------------------------------------------------------
/core/common/src/androidMain/kotlin/io/github/sadellie/sukko/core/common/PlatformFileUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import android.net.Uri
4 | import io.github.vinceglb.filekit.FileKit
5 | import io.github.vinceglb.filekit.PlatformFile
6 | import io.github.vinceglb.filekit.context
7 | import io.github.vinceglb.filekit.dialogs.toAndroidUri
8 |
9 | fun PlatformFile.uri(): Uri {
10 | // from filekit 0.10.0
11 | val authority = "${FileKit.context.packageName}.filekit.fileprovider"
12 | return this.toAndroidUri(authority)
13 | }
14 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/values-night-v34/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_background_dark
4 | @android:color/system_on_background_dark
5 | @android:color/system_surface_container_dark
6 | @android:color/system_surface_bright_dark
7 | @android:color/system_on_surface_dark
8 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/values-v34/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_background_light
4 | @android:color/system_on_background_light
5 | @android:color/system_surface_container_light
6 | @android:color/system_surface_bright_light
7 | @android:color/system_on_surface_light
8 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/scripteditor/TextFieldStateUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector.scripteditor
2 |
3 | import androidx.compose.foundation.text.input.TextFieldState
4 | import androidx.compose.ui.text.TextRange
5 |
6 | /** Paste [text]. Like pasting in any other app: insert at selection and update selection */
7 | internal fun TextFieldState.insert(text: String) = edit {
8 | replace(selection.start, selection.end, text)
9 | selection = TextRange(selection.end)
10 | }
11 |
--------------------------------------------------------------------------------
/core/fontfiles/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/fontfiles/FontFamilyLoader.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.fontfiles
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.text.font.FontFamily
5 | import io.github.sadellie.sukko.core.common.notReady
6 | import okio.Path
7 |
8 | actual class FontFamilyLoader actual constructor() {
9 | actual suspend fun loadFromFontFile(fontFile: FontFile, fileDirPath: Path): FontFamily = notReady
10 |
11 | @Composable actual fun rememberFontFamily(fontFile: FontFile): FontFamily = notReady
12 | }
13 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/error_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/core/medialistener/src/androidMain/kotlin/io/github/sadellie/sukko/core/medialistener/MediaListener.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.medialistener
2 |
3 | import io.github.sadellie.sukko.core.model.basic.MediaInfo
4 |
5 | interface MediaListener {
6 | companion object {
7 | const val MEDIA_METADATA_UPDATE = "MEDIA_METADATA_UPDATE"
8 | }
9 |
10 | fun startListening()
11 |
12 | fun destroy()
13 |
14 | fun pause()
15 |
16 | fun play()
17 |
18 | fun seek(timestamp: Long)
19 |
20 | fun skipToNext()
21 |
22 | fun skipToPrevious()
23 |
24 | fun openPlayer()
25 |
26 | fun getMediaInfo(): MediaInfo?
27 | }
28 |
--------------------------------------------------------------------------------
/feature/editor/src/wasmJsMain/kotlin/io/github/sadellie/sukko/feature/editor/parameters/EditorParametersImageLayer.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.parameters
2 |
3 | import androidx.compose.runtime.Composable
4 | import io.github.sadellie.sukko.core.common.notReady
5 | import io.github.sadellie.sukko.core.model.Globals
6 | import io.github.sadellie.sukko.core.model.layer.ColdImageLayer
7 |
8 | @Composable
9 | internal actual fun EditorParametersImageUri(
10 | onUpdateLayer: (ColdImageLayer) -> Unit,
11 | layer: ColdImageLayer,
12 | compactListMode: Boolean,
13 | globals: Globals,
14 | ) {
15 | notReady
16 | }
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g
2 | kotlin.daemon.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=320m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g
3 | org.gradle.parallel=true
4 | org.gradle.caching=true
5 | org.gradle.configuration-cache=true
6 | org.gradle.configuration-cache.problems=warn
7 | android.useAndroidX=true
8 | kotlin.code.style=official
9 | android.nonTransitiveRClass=false
10 | android.defaults.buildfeatures.resvalues=false
11 | android.defaults.buildfeatures.shaders=false
12 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Uncomment this to preserve the line number information for
2 | # debugging stack traces.
3 | #-keepattributes SourceFile,LineNumberTable
4 |
5 | # If you keep the line number information, uncomment this to
6 | # hide the original source file name.
7 | #-renamesourcefileattribute SourceFile
8 |
9 | # https://issuetracker.google.com/u/2/issues/371227633
10 | # https://github.com/Kotlin/kotlinx.serialization/issues/2825
11 | -repackageclasses
12 | #-keep @kotlinx.serialization.Serializable class * {*;}
13 | -if @kotlinx.serialization.Serializable class **
14 | -keepclassmembers class <1> {
15 | public static <1> INSTANCE;
16 | kotlinx.serialization.KSerializer serializer(...);
17 | }
--------------------------------------------------------------------------------
/app/src/androidMain/res/xml/widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/res/layout/default_initial_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/core/database/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.ksp)
4 | alias(libs.plugins.room)
5 | }
6 |
7 | android { namespace = "io.github.sadellie.sukko.core.database" }
8 |
9 | dependencies {
10 | ksp(libs.androidx.room.compiler)
11 | implementation(libs.androidx.room.ktx)
12 | implementation(libs.androidx.room.runtime)
13 | implementation(project(":core:common"))
14 | testImplementation(libs.kotlin.test)
15 | testImplementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.test)
16 | }
17 |
18 | room {
19 | val schemaLocation = "$projectDir/schemas"
20 | schemaDirectory(schemaLocation)
21 | println("Exported Database schema to $schemaLocation")
22 | }
23 |
--------------------------------------------------------------------------------
/core/script/src/androidUnitTest/kotlin/io/github/sadellie/sukko/core/script/docs/DocsRepositoryImplTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.docs
2 |
3 | import kotlinx.coroutines.test.runTest
4 | import okio.Path.Companion.toPath
5 | import kotlin.test.Test
6 |
7 | class DocsRepositoryImplTest {
8 | @Test
9 | fun loadDocs() = runTest {
10 | val repo = DocsRepositoryImpl()
11 | val dir = System.getProperty("user.dir") ?: error("No used.dir")
12 | val scriptingFilePath = dir.toPath() / "src/commonMain/composeResources/files/scripting.json"
13 | scriptingFilePath.toFile().inputStream().use {
14 | // should not fail
15 | repo.parseAndUpdateDocs(it)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/common/src/wasmJsMain/kotlin/io/github/sadellie/sukko/core/common/FlowUtils.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.collectAsState
6 | import androidx.lifecycle.Lifecycle
7 | import kotlin.coroutines.CoroutineContext
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.flow.StateFlow
10 |
11 | actual val defaultIODispatcher = Dispatchers.Default
12 |
13 | @Composable
14 | actual fun StateFlow.collectAsStateWithLifecycleKMP(
15 | initialValue: T,
16 | minActiveState: Lifecycle.State,
17 | context: CoroutineContext,
18 | ): State = collectAsState(initial = initialValue, context = context)
19 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Sukko docs
2 | site_url: https://io.github.sadellie/sukko/
3 | docs_dir: ./docs
4 | repo_url: https://github.com/sadellie/sukko/
5 | repo_name: sukko
6 | theme:
7 | palette:
8 | scheme: slate
9 | name: material
10 | icon:
11 | repo: fontawesome/brands/github
12 | logo:
13 | logo2.svg
14 | favicon:
15 | logo2.svg
16 | features:
17 | - navigation.instant
18 | - navigation.instant.progress
19 | - navigation.instant.prefetch
20 | - navigation.top
21 | extra_css:
22 | - stylesheets/extra.css
23 | markdown_extensions:
24 | - admonition
25 | - pymdownx.details
26 | - pymdownx.superfences
27 | nav:
28 | - Home: index.md
29 | - Scripting: scripting.md
30 | - Press kit: press.md
31 | - Privacy policy: privacy.md
--------------------------------------------------------------------------------
/core/designsystem/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | kotlin {
8 | sourceSets.commonMain.dependencies {
9 | implementation(compose.components.uiToolingPreview)
10 | implementation(compose.foundation)
11 | implementation(libs.com.squareup.okio.okio)
12 | implementation(libs.io.coil.kt.coil3.coil.compose)
13 | implementation(libs.io.coil.kt.coil3.coil.svg)
14 | implementation(libs.org.jetbrains.compose.material3.material3)
15 | implementation(libs.org.jetbrains.compose.material3.material3.window.size)
16 | implementation(project(":themmo"))
17 | }
18 | }
19 |
20 | android { namespace = "io.github.sadellie.sukko.core.designsystem" }
21 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/io/github/sadellie/sukko/core/database/SukkoDatabase.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import io.github.sadellie.sukko.core.common.SCHEMA_VERSION
6 |
7 | @Database(
8 | version = SCHEMA_VERSION,
9 | exportSchema = true,
10 | entities = [WidgetDataBased::class, WidgetDataPresetBased::class, IconPackBased::class],
11 | )
12 | abstract class SukkoDatabase : RoomDatabase() {
13 | abstract fun widgetDataDao(): WidgetDataDao
14 |
15 | abstract fun widgetDataPresetDao(): WidgetDataPresetDao
16 |
17 | abstract fun iconPackDao(): IconPackDao
18 |
19 | companion object {
20 | /** Do NOT change */
21 | const val DATABASE_NAME = "sukko.db"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/medialistener/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/FunctionNode.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script
2 |
3 | import io.github.sadellie.sukko.core.script.token.Token3
4 |
5 | internal data class FunctionNode(
6 | override val token: Token3.Function,
7 | override val children: List,
8 | ) : ASTNode {
9 | constructor(token: Token3.Function, vararg children: ASTNode) : this(token, children.asList())
10 |
11 | override fun withNewChildren(children: List) = this.copy(children = children)
12 |
13 | override fun toFormattedString(): String {
14 | val formattedChildrenString =
15 | children.joinToString("${Token3.Comma.symbol} ", transform = ASTNode::toFormattedString)
16 | return "${token.symbol}($formattedChildrenString)"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/Utils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import androidx.compose.runtime.rememberCoroutineScope
6 | import io.github.sadellie.sukko.core.data.LayerContextProvider
7 | import io.github.sadellie.sukko.core.model.LayerContext
8 |
9 | /**
10 | * Create an instance of [LayerContext]. Doesn't survive configuration changes.
11 | *
12 | * @return Remembered [LayerContext]
13 | */
14 | @Composable
15 | internal fun rememberLayerContext(): LayerContext {
16 | val coroutineScope = rememberCoroutineScope()
17 | return remember { LayerContextProvider().provide(coroutineScope.coroutineContext) }
18 | }
19 |
--------------------------------------------------------------------------------
/core/data/src/commonMain/kotlin/io/github/sadellie/sukko/core/data/WidgetDataPresetCustomRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import io.github.sadellie.sukko.core.model.WidgetDataPreset
4 | import kotlinx.coroutines.flow.Flow
5 | import okio.Path
6 |
7 | interface WidgetDataPresetCustomRepository {
8 | fun allWidgetDataPresets(decodeExtra: Boolean = true): Flow>
9 |
10 | suspend fun loadByPresetId(presetId: Long): WidgetDataPreset.Custom?
11 |
12 | /** Create new preset. Preview image will be copied from [previewPath]. */
13 | suspend fun insertNewWithPreview(
14 | widgetDataPreset: WidgetDataPreset.Custom,
15 | previewPath: Path?,
16 | ): Long
17 |
18 | suspend fun rename(presetId: Long, newName: String)
19 |
20 | suspend fun delete(presetId: Long)
21 | }
22 |
--------------------------------------------------------------------------------
/core/medialistener/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("sukko.multiplatform.library") }
2 |
3 | kotlin {
4 | sourceSets.commonMain.dependencies {
5 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
6 | implementation(libs.co.touchlab.kermit)
7 | implementation(libs.com.squareup.okio.okio)
8 | implementation(libs.io.insert.koin.koin.core)
9 | implementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.core)
10 | implementation(project(":core:common"))
11 | implementation(project(":core:model"))
12 | }
13 | sourceSets.androidMain.dependencies {
14 | implementation(libs.androidx.core.ktx)
15 | implementation("androidx.media3:media3-session:1.8.0")
16 | implementation("androidx.media3:media3-datasource:1.8.0")
17 | }
18 | }
19 |
20 | android { namespace = "io.github.sadellie.sukko.core.medialistener" }
21 |
--------------------------------------------------------------------------------
/core/common/src/androidMain/kotlin/io/github/sadellie/sukko/core/common/FlowUtils.android.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
7 | import kotlin.coroutines.CoroutineContext
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.flow.StateFlow
10 |
11 | actual val defaultIODispatcher = Dispatchers.IO
12 |
13 | @Composable
14 | actual fun StateFlow.collectAsStateWithLifecycleKMP(
15 | initialValue: T,
16 | minActiveState: Lifecycle.State,
17 | context: CoroutineContext,
18 | ): State =
19 | collectAsStateWithLifecycle(
20 | initialValue = initialValue,
21 | minActiveState = minActiveState,
22 | context = context,
23 | )
24 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches:
5 | - master
6 | permissions:
7 | contents: write
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Configure Git Credentials
14 | run: |
15 | git config user.name github-actions[bot]
16 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com
17 | - uses: actions/setup-python@v5
18 | with:
19 | python-version: 3.x
20 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
21 | - uses: actions/cache@v4
22 | with:
23 | key: mkdocs-material-${{ env.cache_id }}
24 | path: ~/.cache
25 | restore-keys: |
26 | mkdocs-material-
27 | - run: pip install mkdocs-material
28 | - run: mkdocs gh-deploy --force
--------------------------------------------------------------------------------
/core/medialistener/src/androidMain/kotlin/io/github/sadellie/sukko/core/medialistener/BitmapUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.medialistener
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.net.Uri
6 | import co.touchlab.kermit.Logger
7 | import io.github.sadellie.sukko.core.common.cachePath
8 | import java.io.BufferedOutputStream
9 |
10 | /** Cache bitmap in temp folder and return URI to file as string. */
11 | internal fun Bitmap.cache(context: Context, fileName: String): String {
12 | val tempFile = (context.cachePath / "$fileName.png").toFile()
13 | tempFile.parentFile?.mkdirs()
14 | val tempFileUri = Uri.fromFile(tempFile).toString()
15 | Logger.d("BitmapUtils") { "cache update: $tempFileUri" }
16 | BufferedOutputStream(tempFile.outputStream()).use {
17 | this@cache.compress(Bitmap.CompressFormat.PNG, 0, it)
18 | }
19 | return tempFileUri
20 | }
21 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/BracketsNode.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script
2 |
3 | import io.github.sadellie.sukko.core.script.token.Token3
4 |
5 | internal data class BracketsNode(override val children: List) : ASTNode {
6 | override val token = Token3.Parentheses.Left
7 |
8 | constructor(child: ASTNode) : this(listOf(child))
9 |
10 | override fun withNewChildren(children: List) = this.copy(children = children)
11 |
12 | override fun toFormattedString(): String {
13 | val child = children.first().toFormattedString()
14 | return "($child)"
15 | }
16 |
17 | override fun collapse(scriptContext: ScriptContext): ASTNode {
18 | val child = children.firstOrNull()
19 | // extract atomic children to remove unnecessary bracket
20 | if (child is AtomicNode) return child
21 | return super.collapse(scriptContext)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets.commonMain.dependencies {
10 | implementation(compose.components.resources)
11 | implementation(compose.foundation)
12 | implementation(libs.co.touchlab.kermit)
13 | implementation(libs.com.squareup.okio.okio)
14 | implementation(libs.io.github.vinceglb.filekit.dialogs)
15 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
16 | }
17 | sourceSets.androidMain.dependencies { implementation(libs.androidx.core.ktx) }
18 | sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) }
19 | }
20 |
21 | compose.resources {
22 | packageOfResClass = "io.github.sadellie.sukko.resources"
23 | publicResClass = true
24 | }
25 |
26 | android { namespace = "io.github.sadellie.sukko.core.common" }
27 |
--------------------------------------------------------------------------------
/app/src/commonMain/kotlin/io/github/sadellie/sukko/Module.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko
2 |
3 | import io.github.sadellie.sukko.core.data.IconPackCustomRepository
4 | import io.github.sadellie.sukko.core.data.InstalledAppsProvider
5 | import io.github.sadellie.sukko.core.data.WidgetDataPresetCustomRepository
6 | import io.github.sadellie.sukko.core.data.WidgetDataRepository
7 | import io.github.sadellie.sukko.core.widget.WidgetInfoRepository
8 | import org.koin.core.definition.KoinDefinition
9 | import org.koin.core.module.Module
10 |
11 | expect fun Module.widgetInfoRepository(): KoinDefinition
12 |
13 | expect fun Module.widgetDataRepository(): KoinDefinition
14 |
15 | expect fun Module.widgetDataPresetRepository(): KoinDefinition
16 |
17 | expect fun Module.iconPackRepository(): KoinDefinition
18 |
19 | expect fun Module.installedAppsRepository(): KoinDefinition
20 |
--------------------------------------------------------------------------------
/core/data/src/commonMain/kotlin/io/github/sadellie/sukko/core/data/WidgetDataRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import androidx.compose.ui.graphics.ImageBitmap
4 | import io.github.sadellie.sukko.core.model.WidgetData
5 | import io.github.sadellie.sukko.core.model.layer.Layer
6 | import kotlinx.coroutines.flow.Flow
7 | import okio.Path
8 |
9 | interface WidgetDataRepository {
10 | fun allWidgetData(decodeExtra: Boolean = true): Flow>
11 |
12 | suspend fun loadByAppWidgetId(appWidgetId: Int): WidgetData?
13 |
14 | suspend fun save(
15 | widgetData: WidgetData,
16 | evaluatedLayers: List,
17 | previewImageBitmap: ImageBitmap?,
18 | )
19 |
20 | suspend fun rename(appWidgetId: Int, newName: String)
21 |
22 | suspend fun delete(appWidgetId: Int)
23 |
24 | /** Return full path to preview from [WidgetData.getPreviewPath] using actual device folder. */
25 | suspend fun getPreview(widgetData: WidgetData): Path?
26 | }
27 |
--------------------------------------------------------------------------------
/core/fontfiles/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets.commonMain.dependencies {
10 | implementation(compose.components.resources)
11 | implementation(compose.components.uiToolingPreview)
12 | implementation(compose.foundation)
13 | implementation(libs.co.touchlab.kermit)
14 | implementation(libs.com.squareup.okio.okio)
15 | implementation(libs.io.github.vinceglb.filekit.core)
16 | implementation(libs.org.jetbrains.compose.material3.material3)
17 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
18 | implementation(project(":core:common"))
19 | implementation(project(":core:designsystem"))
20 | implementation(project(":core:ui"))
21 | }
22 | }
23 |
24 | compose.resources.generateResClass = compose.resources.never
25 |
26 | android { namespace = "io.github.sadellie.sukko.core.filefiles" }
27 |
--------------------------------------------------------------------------------
/themmo/src/androidMain/kotlin/io/github/sadellie/themmo/ProvideColorScheme.android.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.themmo
2 |
3 | import android.os.Build
4 | import androidx.compose.material3.ColorScheme
5 | import androidx.compose.material3.dynamicDarkColorScheme
6 | import androidx.compose.material3.dynamicLightColorScheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.platform.LocalContext
9 | import androidx.compose.ui.res.colorResource
10 | import io.github.sadellie.themmo.core.MonetMode
11 |
12 | @Composable
13 | actual fun provideDynamicColorScheme(isDark: Boolean): ColorScheme {
14 | val context = LocalContext.current
15 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
16 | if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
17 | } else {
18 | generateColorScheme(
19 | keyColor = colorResource(android.R.color.system_accent1_500),
20 | isDark = isDark,
21 | style = MonetMode.TonalSpot,
22 | )
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/BUILD.md:
--------------------------------------------------------------------------------
1 | # Icon packs
2 |
3 | Icon packs are not included in VC. Manually download icons and use the script below to copy them into the app.
4 |
5 | ```bash
6 | gradlew generateIconPackFiles -Pdirectory=/absolute/path -Poutput=/absolute/path
7 | ```
8 |
9 | Valid values may change in the future.
10 |
11 | - `size` - optical size in px. Only existing sizes, check source folder
12 | - `style` - `outlined`, `rounded` or `sharp`
13 | - `directory` - path to folder with SVGs. Absolute path preferred
14 | - `output` - path for generated icon pack files. Absolute path preferred
15 |
16 | # Useful commands
17 |
18 | Regenerate resources without building entire project
19 |
20 | ```bash
21 | gradlew generateResourceAccessorsForCommonMain
22 | ```
23 |
24 | Run common unit tests
25 |
26 | ```bash
27 | gradlew testDebugUnitTest
28 | # or specify
29 | gradlew testDebugUnitTest --tests "package.name.here.*"
30 | ```
31 |
32 | # Code quality and formatting
33 |
34 | - `ktfmt` with Google style
35 | - `ktlint` with tasks declared in this project
36 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/data/MediaInfoProvider.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.data
2 |
3 | /** Provider for currently playing media information */
4 | interface MediaInfoProvider {
5 | /** Media artist */
6 | val artist: String?
7 | /** Media title. For example, name of a song */
8 | val title: String?
9 | /** Media duration. Can be any number */
10 | val durationSeconds: Long?
11 | /** Media position. Can be any number */
12 | val positionSeconds: Long?
13 | /** Media cover uri to locally accessible file */
14 | val coverUri: String?
15 | /** Player icon uri to locally accessible file */
16 | val playerIcon: String?
17 | /** Player name, app name */
18 | val playerName: String?
19 | /** Player status. From PlaybackState */
20 | val playerState: String?
21 | /** Minimal music volume */
22 | val volumeMusicMin: Int
23 | /** Current music volume */
24 | val volumeMusic: Int
25 | /** Maximum music volume */
26 | val volumeMusicMax: Int
27 | }
28 |
--------------------------------------------------------------------------------
/core/unglance/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | android { namespace = "io.github.sadellie.sukko.core.unglance" }
8 |
9 | dependencies {
10 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
11 | implementation(compose.ui)
12 | implementation(libs.androidx.concurrent.concurrent.futures.ktx)
13 | implementation(libs.androidx.lifecycle.lifecycle.process)
14 | implementation(libs.androidx.work.work.runtime.ktx)
15 | implementation(libs.co.touchlab.kermit)
16 | implementation(libs.com.squareup.okio.okio)
17 | implementation(libs.io.coil.kt.coil3.coil.compose)
18 | implementation(libs.io.insert.koin.koin.compose)
19 | implementation(libs.io.insert.koin.koin.core)
20 | implementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.core)
21 | implementation(project(":core:common"))
22 | implementation(project(":core:designsystem"))
23 | implementation(project(":core:model"))
24 | }
25 |
--------------------------------------------------------------------------------
/docs/logo.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/core/data/src/commonMain/kotlin/io/github/sadellie/sukko/core/data/IconPackCustomRepository.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import io.github.sadellie.sukko.core.iconfiles.IconFile
4 | import io.github.sadellie.sukko.core.iconfiles.IconPack
5 | import io.github.vinceglb.filekit.PlatformFile
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface IconPackCustomRepository {
9 | fun getAll(): Flow>
10 |
11 | fun getIconFiles(iconPack: IconPack): Flow>
12 |
13 | suspend fun deleteIconPack(iconPack: IconPack.Custom)
14 |
15 | suspend fun renameIconPack(iconPack: IconPack.Custom, newName: String)
16 |
17 | suspend fun deleteIconFile(iconFile: IconFile)
18 |
19 | suspend fun renameIconFile(iconFile: IconFile, newName: String)
20 |
21 | suspend fun addNewIconPack(iconPack: IconPack.Custom): IconPack.Custom
22 |
23 | suspend fun importIconFiles(iconPack: IconPack.Custom, iconFiles: List)
24 |
25 | suspend fun getIconFilesFromIconPack(iconPack: IconPack): List
26 | }
27 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/RemoveButton.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.material3.Icon
5 | import androidx.compose.material3.IconButton
6 | import androidx.compose.material3.IconButtonDefaults
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import google.material.design.symbols.Close
10 | import google.material.design.symbols.Symbols
11 |
12 | @Composable
13 | fun RemoveButton(onRemoveClick: () -> Unit, enabled: Boolean = true) {
14 | IconButton(
15 | modifier =
16 | Modifier.size(
17 | IconButtonDefaults.smallContainerSize(IconButtonDefaults.IconButtonWidthOption.Narrow)
18 | ),
19 | onClick = onRemoveClick,
20 | shapes = IconButtonDefaults.shapes(),
21 | enabled = enabled,
22 | ) {
23 | Icon(
24 | imageVector = Symbols.Close,
25 | contentDescription = null,
26 | modifier = Modifier.size(IconButtonDefaults.smallIconSize),
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | kotlin {
8 | sourceSets.commonMain.dependencies {
9 | implementation(compose.components.resources)
10 | implementation(compose.components.uiToolingPreview)
11 | implementation(compose.foundation)
12 | implementation(libs.com.composables.core)
13 | implementation(libs.io.coil.kt.coil3.coil.compose)
14 | implementation(libs.org.jetbrains.compose.material3.material3)
15 | implementation(libs.org.jetbrains.compose.material3.material3.window.size)
16 | implementation(project(":core:common"))
17 | implementation(project(":core:designsystem"))
18 | implementation(project(":material-symbols"))
19 | }
20 | sourceSets.androidMain.dependencies {
21 | implementation(compose.uiTooling)
22 | implementation(libs.androidx.activity.compose)
23 | }
24 | }
25 |
26 | compose.resources.generateResClass = compose.resources.never
27 |
28 | android { namespace = "io.github.sadellie.sukko.core.ui" }
29 |
--------------------------------------------------------------------------------
/app/src/wasmJsMain/kotlin/io/github/sadellie/sukko/Module.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko
2 |
3 | import io.github.sadellie.sukko.core.common.notReady
4 | import io.github.sadellie.sukko.core.data.IconPackCustomRepository
5 | import io.github.sadellie.sukko.core.data.InstalledAppsProvider
6 | import io.github.sadellie.sukko.core.data.WidgetDataPresetCustomRepository
7 | import io.github.sadellie.sukko.core.data.WidgetDataRepository
8 | import io.github.sadellie.sukko.core.widget.WidgetInfoRepository
9 | import org.koin.core.definition.KoinDefinition
10 | import org.koin.core.module.Module
11 |
12 | actual fun Module.widgetInfoRepository(): KoinDefinition = notReady
13 |
14 | actual fun Module.widgetDataRepository(): KoinDefinition = notReady
15 |
16 | actual fun Module.widgetDataPresetRepository(): KoinDefinition =
17 | notReady
18 |
19 | actual fun Module.iconPackRepository(): KoinDefinition = notReady
20 |
21 | actual fun Module.installedAppsRepository(): KoinDefinition = notReady
22 |
--------------------------------------------------------------------------------
/docs/logo2.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/data/DynamicColorSchemeProvider.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.data
2 |
3 | import androidx.compose.material3.ColorScheme
4 | import androidx.compose.ui.graphics.Color
5 | import io.github.sadellie.sukko.core.common.toHex
6 | import io.github.sadellie.sukko.core.model.basic.M3Color
7 |
8 | interface DynamicColorSchemeProvider {
9 | fun extractHexFromSystemColorScheme(m3ColorName: String): String
10 |
11 | suspend fun extractHexFromImageColorScheme(m3ColorName: String, imageUri: String): String
12 |
13 | fun getColorFromSystemColorScheme(m3Color: M3Color): Color
14 |
15 | /**
16 | * Returns hex color from color scheme. Pass [M3Color.name].
17 | *
18 | * Empty string is ONLY to simplify test setups. Actual implementation never returns empty
19 | * strings.
20 | */
21 | fun extractHexFromColorScheme(m3ColorName: String, colorScheme: ColorScheme): String {
22 | val m3Color = M3Color.valueOf(m3ColorName)
23 | val color = m3Color.extractFromScheme(colorScheme)
24 | val colorHex = color.toHex()
25 | return colorHex
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/feature/editor/src/wasmJsMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/scripteditor/InputPage.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector.scripteditor
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.material3.Icon
5 | import androidx.compose.material3.IconButton
6 | import androidx.compose.material3.IconButtonDefaults
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import google.material.design.symbols.AddPhotoAlternate
10 | import google.material.design.symbols.Symbols
11 |
12 | @Composable
13 | internal actual fun AddImageLinkButton(onAdd: (String) -> Unit) {
14 | IconButton(
15 | modifier =
16 | Modifier.size(
17 | IconButtonDefaults.smallContainerSize(IconButtonDefaults.IconButtonWidthOption.Uniform)
18 | ),
19 | onClick = {},
20 | shapes = IconButtonDefaults.shapes(),
21 | enabled = false,
22 | ) {
23 | Icon(
24 | imageVector = Symbols.AddPhotoAlternate,
25 | contentDescription = null,
26 | modifier = Modifier.size(IconButtonDefaults.smallIconSize),
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/widget/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | kotlin {
8 | sourceSets.commonMain.dependencies {
9 | implementation(compose.ui)
10 | implementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.core)
11 | }
12 | sourceSets.androidMain.dependencies {
13 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
14 | implementation(libs.androidx.core.ktx)
15 | implementation(libs.co.touchlab.kermit)
16 | implementation(libs.io.insert.koin.koin.core)
17 | implementation(libs.org.jetbrains.kotlinx.kotlinx.datetime)
18 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
19 | implementation(project(":core:common"))
20 | implementation(project(":core:data"))
21 | implementation(project(":core:medialistener"))
22 | implementation(project(":core:model"))
23 | implementation(project(":core:unglance"))
24 | }
25 | sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) }
26 | }
27 |
28 | android { namespace = "io.github.sadellie.sukko.core.widget" }
29 |
--------------------------------------------------------------------------------
/app/src/androidMain/kotlin/io/github/sadellie/sukko/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
8 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
9 | import coil3.ImageLoader
10 | import io.github.sadellie.sukko.core.common.filesPath
11 | import io.github.sadellie.sukko.feature.home.HomeRoute
12 | import org.koin.android.ext.android.get
13 |
14 | class MainActivity : ComponentActivity() {
15 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | enableEdgeToEdge()
18 | super.onCreate(savedInstanceState)
19 | setContent {
20 | MainApp(
21 | onLastRoutePop = { this.finish() },
22 | windowsSize = calculateWindowSizeClass(this),
23 | imageLoader = get(),
24 | filesDirPath = this.filesPath,
25 | initialRoute = HomeRoute,
26 | )
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/kotlin/io/github/sadellie/sukko/core/widget/PowerStateReceiver.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.widget
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.PowerManager
7 | import co.touchlab.kermit.Logger
8 |
9 | /** When device changes power state, ask widget to reset alarm manager */
10 | class PowerStateReceiver : BroadcastReceiver() {
11 | companion object {
12 | private const val TAG = "PowerStateReceiver"
13 | }
14 |
15 | override fun onReceive(context: Context?, intent: Intent?) {
16 | intent ?: return
17 | context ?: return
18 | when (intent.action) {
19 | Intent.ACTION_POWER_CONNECTED,
20 | Intent.ACTION_POWER_DISCONNECTED,
21 | PowerManager.ACTION_POWER_SAVE_MODE_CHANGED -> {
22 | val widgetIntent =
23 | Intent(context, MainWidgetProvider::class.java)
24 | .setAction(MainWidgetProvider.ACTION_POWER_UPDATE)
25 | context.applicationContext.sendBroadcast(widgetIntent)
26 | }
27 | else -> Logger.d(TAG) { "Unexpected action: ${intent.action}" }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/ImportingIconPack.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model
2 |
3 | import io.github.sadellie.sukko.core.iconfiles.IconPack
4 | import kotlin.uuid.ExperimentalUuidApi
5 | import okio.Path
6 |
7 | @OptIn(ExperimentalUuidApi::class)
8 | data class ImportingWidgetDataPreset(
9 | val widgetDataPreset: WidgetDataPreset.Custom,
10 | val fullPreviewPath: Path?,
11 | val importingIconPacks: List,
12 | val importingFontFiles: List,
13 | )
14 |
15 | data class ImportingIconPack(
16 | val importingId: Long,
17 | val importingName: String,
18 | val action: ImportingIconPackAction,
19 | )
20 |
21 | data class ImportingFontFile(val importingName: String, val import: Boolean)
22 |
23 | sealed interface ImportingIconPackAction {
24 | data object CreateNew : ImportingIconPackAction
25 |
26 | data class Merge(val destinationIconPack: IconPack.Custom) : ImportingIconPackAction
27 |
28 | companion object {
29 | fun actions(customIconPacks: List): List =
30 | listOf(CreateNew) + customIconPacks.map { Merge(it) }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/importexport/src/main/kotlin/io/github/sadellie/sukko/core/importexport/DataMigration.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.importexport
2 |
3 | import io.github.sadellie.sukko.core.common.SCHEMA_VERSION
4 | import io.github.sadellie.sukko.core.model.WidgetDataPreset
5 | import kotlinx.serialization.json.Json
6 | import kotlinx.serialization.json.decodeFromJsonElement
7 |
8 | fun consumeAndMigrateWidgetDataPreset(widgetDataPreset: String, from: Int): WidgetDataPreset.Custom {
9 | if (from > SCHEMA_VERSION) error("Unsupported schema version: $from, max is $SCHEMA_VERSION")
10 | if (from == SCHEMA_VERSION) return Json.decodeFromString(widgetDataPreset)
11 | var json = Json.parseToJsonElement(widgetDataPreset)
12 | // var currentSchemaVersion = from
13 | // if (currentSchemaVersion == 2) {
14 | // // migrate to 3
15 | // val result = json.jsonObject.toMutableMap()
16 | // result.remove("fullPreviewPath")
17 | // json = JsonObject(result)
18 | // // set to chain migration steps
19 | // currentSchemaVersion = 3
20 | // }
21 |
22 | val widgetDataPreset = Json.decodeFromJsonElement(json)
23 |
24 | return widgetDataPreset
25 | }
26 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyMultiplyNumbersTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.MultiplyNode
5 | import io.github.sadellie.sukko.core.script.NumberNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import kotlin.test.Test
8 |
9 | class SimplifyMultiplyNumbersTest {
10 | @Test fun simplify_multiplyNumbers1() = assertSimplify("2 * 3", NumberNode(6.0))
11 |
12 | @Test fun simplify_multiplyNumbers2() = assertSimplify("2 * 3 * 4", NumberNode(24.0))
13 |
14 | @Test fun simplify_multiplyNumbers3() = assertSimplify("2 / 3 + 4", null)
15 |
16 | @Test
17 | fun simplify_multiplyNumbers4() =
18 | assertSimplify("2 * (3 * 4)", MultiplyNode(NumberNode(2), NumberNode(12.0)))
19 |
20 | private fun assertSimplify(input: String, expected: ASTNode?) {
21 | assertSimplifySingleRule(
22 | rule = SimplifyMultiplyNumbers,
23 | simplificationType = SimplificationType.PRODUCT_OF_NUMBERS,
24 | input = input,
25 | expected = expected,
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/NavigateUpButton.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.material3.Icon
5 | import androidx.compose.material3.IconButton
6 | import androidx.compose.material3.IconButtonDefaults
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import google.material.design.symbols.ArrowBack
10 | import google.material.design.symbols.Symbols
11 |
12 | /**
13 | * Button that is used in Top bars
14 | *
15 | * @param onClick Action to be called when button is clicked.
16 | */
17 | @Composable
18 | fun NavigateUpButton(onClick: () -> Unit, enabled: Boolean = true) {
19 | IconButton(
20 | onClick = onClick,
21 | enabled = enabled,
22 | shapes = IconButtonDefaults.shapes(),
23 | modifier =
24 | Modifier.size(
25 | IconButtonDefaults.smallContainerSize(IconButtonDefaults.IconButtonWidthOption.Uniform)
26 | ),
27 | ) {
28 | Icon(
29 | imageVector = Symbols.ArrowBack,
30 | contentDescription = null,
31 | modifier = Modifier.size(IconButtonDefaults.smallIconSize),
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/common/src/androidMain/kotlin/io/github/sadellie/sukko/core/common/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import android.os.FileObserver
4 | import java.io.File
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.channels.awaitClose
7 | import kotlinx.coroutines.flow.callbackFlow
8 | import kotlinx.coroutines.flow.distinctUntilChanged
9 | import kotlinx.coroutines.flow.flowOn
10 | import kotlinx.coroutines.launch
11 |
12 | fun fileObserverFlow(file: File, onEventCallback: suspend (event: Int, path: String?) -> T) =
13 | callbackFlow {
14 | val listener =
15 | object : FileObserver(file, CREATE or DELETE or MOVED_TO) {
16 | override fun onEvent(event: Int, path: String?) {
17 | this@callbackFlow.launch {
18 | val value = onEventCallback(event, path)
19 | trySend(value)
20 | }
21 | }
22 |
23 | override fun startWatching() {
24 | super.startWatching()
25 | onEvent(0, null)
26 | }
27 | }
28 | listener.startWatching()
29 | awaitClose { listener.stopWatching() }
30 | }
31 | .distinctUntilChanged()
32 | .flowOn(Dispatchers.IO)
33 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/io/github/sadellie/sukko/core/database/IconPackBased.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.database
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Dao
5 | import androidx.room.Entity
6 | import androidx.room.Insert
7 | import androidx.room.OnConflictStrategy
8 | import androidx.room.PrimaryKey
9 | import androidx.room.Query
10 | import kotlinx.coroutines.flow.Flow
11 |
12 | @Entity(tableName = "icon_pack")
13 | data class IconPackBased(
14 | @PrimaryKey(autoGenerate = true) @ColumnInfo("iconPackId") val iconPackId: Long,
15 | @ColumnInfo("name") val name: String,
16 | )
17 |
18 | @Dao
19 | interface IconPackDao {
20 | @Query("SELECT * FROM icon_pack") fun getAll(): Flow>
21 |
22 | @Query("DELETE from icon_pack WHERE iconPackId = :iconPackId") fun delete(iconPackId: Long)
23 |
24 | @Query("UPDATE icon_pack SET name = :newName WHERE iconPackId = :iconPackId")
25 | fun rename(iconPackId: Long, newName: String)
26 |
27 | /**
28 | * Insert new [iconPackBased] into database.
29 | *
30 | * @return inserted row id
31 | */
32 | @Insert(onConflict = OnConflictStrategy.ABORT)
33 | suspend fun insertNew(iconPackBased: IconPackBased): Long
34 | }
35 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/ScriptContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script
2 |
3 | interface BasicScriptContext {
4 | val batteryCapacity: Int
5 | val batteryChargeDischargeSeconds: Int
6 | val batteryStatus: String
7 | val currentTimestamp: Long
8 | val deviceModel: String
9 | val mediaArtist: String?
10 | val mediaCoverUri: String?
11 | val mediaDuration: Long?
12 | val mediaPosition: Long?
13 | val mediaTitle: String?
14 | val playerIcon: String?
15 | val playerState: String?
16 | val playerName: String?
17 | val volumeMusicMin: Int
18 | val volumeMusic: Int
19 | val volumeMusicMax: Int
20 |
21 | fun currentDate(format: String): String
22 |
23 | fun currentDateWithTimeZone(format: String, timeZoneId: String): String
24 |
25 | fun formatTimestamp(timeStamp: Long, format: String): String
26 |
27 | fun dynamicColor(m3ColorName: String): String
28 |
29 | suspend fun colorScheme(m3ColorName: String, source: String): String
30 | }
31 |
32 | class ScriptContext(private val scriptContext: BasicScriptContext) :
33 | BasicScriptContext by scriptContext {
34 | internal val variableValueMemory: HashMap = hashMapOf()
35 | }
36 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyPlusNumbersTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.NumberNode
5 | import io.github.sadellie.sukko.core.script.PlusNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import kotlin.test.Test
8 |
9 | class SimplifyPlusNumbersTest {
10 |
11 | @Test fun simplify_plusTwoNumbers1() = assertSimplify("2 + 3", NumberNode(5.0))
12 |
13 | @Test fun simplify_plusTwoNumbers2() = assertSimplify("2 + 3 + 4", NumberNode(9.0))
14 |
15 | @Test fun simplify_plusTwoNumbers3() = assertSimplify("2 + 3 * 4", null)
16 |
17 | @Test fun simplify_plusTwoNumbers4() = assertSimplify("2 / 3 * 4", null)
18 |
19 | @Test
20 | fun simplify_plusTwoNumbers5() =
21 | assertSimplify("2 + (3 + 4)", PlusNode(NumberNode(2), NumberNode(7.0)))
22 |
23 | private fun assertSimplify(input: String, expected: ASTNode?) {
24 | assertSimplifySingleRule(
25 | rule = SimplifyPlusNumbers,
26 | simplificationType = SimplificationType.SUM_OF_NUMBERS,
27 | input = input,
28 | expected = expected,
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/importexport/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.serialization)
4 | }
5 |
6 | android {
7 | namespace = "io.github.sadellie.sukko.core.importexport"
8 | defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
9 | }
10 |
11 | dependencies {
12 | implementation(libs.androidx.core.ktx)
13 | implementation(libs.co.touchlab.kermit)
14 | implementation(libs.com.squareup.okio.okio)
15 | implementation(libs.io.github.vinceglb.filekit.core)
16 | implementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.core)
17 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
18 | implementation(project(":core:common"))
19 | implementation(project(":core:data"))
20 | implementation(project(":core:fontfiles"))
21 | implementation(project(":core:iconfiles"))
22 | implementation(project(":core:model"))
23 | testImplementation(libs.kotlin.test)
24 | testImplementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.test)
25 | androidTestImplementation(libs.androidx.room.testing)
26 | androidTestImplementation(libs.androidx.test.core)
27 | androidTestImplementation(libs.androidx.test.runner)
28 | androidTestImplementation(project(":core:database"))
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/androidMain/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
13 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/io/github/sadellie/sukko/core/database/WidgetDataBased.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.database
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Dao
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import androidx.room.Query
8 | import androidx.room.Upsert
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | @Entity(tableName = "widget_data")
12 | data class WidgetDataBased(
13 | @PrimaryKey @ColumnInfo("appWidgetId") val appWidgetId: Int,
14 | @ColumnInfo("name") val name: String?,
15 | @ColumnInfo("layers") val layers: String,
16 | @ColumnInfo("globals") val globals: String,
17 | )
18 |
19 | @Dao
20 | interface WidgetDataDao {
21 | @Query("SELECT * FROM widget_data") fun getAll(): Flow>
22 |
23 | @Query("SELECT * FROM widget_data WHERE appWidgetId = :appWidgetId LIMIT 1")
24 | suspend fun getById(appWidgetId: Int): WidgetDataBased?
25 |
26 | @Upsert suspend fun save(widgetDataBased: WidgetDataBased)
27 |
28 | @Query("UPDATE widget_data SET name = :newName WHERE appWidgetId = :appWidgetId")
29 | suspend fun rename(appWidgetId: Int, newName: String)
30 |
31 | @Query("DELETE FROM widget_data WHERE appWidgetId = :appWidgetId")
32 | suspend fun deleteById(appWidgetId: Int)
33 | }
34 |
--------------------------------------------------------------------------------
/feature/settings/src/main/kotlin/io/github/sadellie/sukko/feature/settings/Utils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.settings
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.State
7 | import androidx.compose.runtime.produceState
8 | import androidx.compose.ui.platform.LocalContext
9 | import androidx.core.net.toUri
10 | import io.github.sadellie.sukko.core.medialistener.NotificationListener
11 | import kotlinx.coroutines.delay
12 |
13 | @Composable
14 | internal fun isNotificationListenerEnabled(): State {
15 | val context = LocalContext.current
16 | return produceState(NotificationListener.canAccessNotifications(context)) {
17 | while (true) {
18 | value = NotificationListener.canAccessNotifications(context)
19 | delay(NOTIFICATION_LISTENER_UPDATE_RATE)
20 | }
21 | }
22 | }
23 |
24 | internal const val PRIVACY_POLICY_URL = "https://sadellie.github.io/sukko/privacy/"
25 |
26 | /** Open given link in browser */
27 | fun openLink(context: Context, url: String) {
28 | context.startActivity(
29 | Intent(Intent.ACTION_VIEW).setData(url.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
30 | )
31 | }
32 |
33 | private const val NOTIFICATION_LISTENER_UPDATE_RATE = 2_000L
34 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/ClipModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.draw.clip
5 | import androidx.compose.ui.graphics.Shape
6 | import io.github.sadellie.sukko.core.model.Globals
7 | import io.github.sadellie.sukko.core.model.LayerContext
8 | import io.github.sadellie.sukko.core.model.basic.ShapeSource
9 | import io.github.sadellie.sukko.resources.Res
10 | import io.github.sadellie.sukko.resources.core_model_modifier_clip
11 | import kotlinx.serialization.Serializable
12 | import kotlinx.serialization.Transient
13 |
14 | @Serializable
15 | data class ColdClipModifier(
16 | override val id: Int,
17 | val shapeSource: ShapeSource = ShapeSource.CutCornersDp(),
18 | ) : WidgetModifier.Cold {
19 | @Transient override val displayName = Res.string.core_model_modifier_clip
20 |
21 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
22 | EvaluatedClipModifier(id = id, shape = shapeSource.getShape())
23 |
24 | override fun updateId(newId: Int) = this.copy(id = newId)
25 | }
26 |
27 | data class EvaluatedClipModifier(override val id: Int, val shape: Shape) :
28 | WidgetModifier.Evaluated {
29 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.clip(shape = shape)
30 | }
31 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyDivideNumbersTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.DivideNode
5 | import io.github.sadellie.sukko.core.script.MultiplyNode
6 | import io.github.sadellie.sukko.core.script.NumberNode
7 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
8 | import kotlin.test.Test
9 |
10 | class SimplifyDivideNumbersTest {
11 | @Test fun simplify_divideNumbers1() = assertSimplify("6 / 3", NumberNode(2.0))
12 |
13 | @Test fun simplify_divideNumbers2() = assertSimplify("2 * 6 / 3", null)
14 |
15 | @Test fun simplify_divideNumbers3() = assertSimplify("5 / 2", NumberNode(2.5))
16 |
17 | @Test
18 | fun simplify_divideNumbers4() =
19 | assertSimplify("6 / 3 / 2", DivideNode(NumberNode(2.0), NumberNode(2)))
20 |
21 | @Test
22 | fun simplify_divideNumbers5() =
23 | assertSimplify("2 * (6 / 2)", MultiplyNode(NumberNode(2), NumberNode(3.0)))
24 |
25 | private fun assertSimplify(input: String, expected: ASTNode?) {
26 | assertSimplifySingleRule(
27 | rule = SimplifyDivideNumbers,
28 | simplificationType = SimplificationType.DIVISION_OF_NUMBERS,
29 | input = input,
30 | expected = expected,
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyOrBoolean.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.BoolNode
5 | import io.github.sadellie.sukko.core.script.FalseNode
6 | import io.github.sadellie.sukko.core.script.OrNode
7 | import io.github.sadellie.sukko.core.script.ScriptContext
8 | import io.github.sadellie.sukko.core.script.TrueNode
9 |
10 | internal val SimplifyOrBoolean =
11 | object : SimplificationRule {
12 | override suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep? =
13 | simplifyBottomToTop(SimplificationType.LOGICAL_OR, context, tree) { currentNode ->
14 | // only work with logical or node. skip to child and move to neighbour
15 | if (currentNode !is OrNode) return@simplifyBottomToTop null
16 |
17 | // find booleans in this node
18 | val left =
19 | currentNode.getParameter(0)?.toBoolean() ?: return@simplifyBottomToTop null
20 | val right =
21 | currentNode.getParameter(1)?.toBoolean() ?: return@simplifyBottomToTop null
22 | // kill parent node and return result as boolean node
23 | return@simplifyBottomToTop if (left || right) TrueNode else FalseNode
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/docs/press.md:
--------------------------------------------------------------------------------
1 | # Press kit
2 |
3 | ## What is Sukko?
4 |
5 | Sukko is an open source Android app for creating widgets for home and lock screens.
6 |
7 | ## How to install
8 |
9 | Latest version can be downloaded and installed from GitHub:
10 |
11 | [https://github.com/sadellie/sukko/releases/latest](https://github.com/sadellie/sukko/releases/latest)
12 |
13 | or you can build the app yourself.
14 |
15 | ## Current stage
16 |
17 | Despite having most of the core features already implemented, the project is still in **Experimental stage**. Expect breaking changes until first full release.
18 |
19 | ## Developer contact
20 |
21 | Feel free to [contact me](https://sadellie.github.io/#contact). Prefer email.
22 |
23 | ## Media
24 |
25 | ### App name
26 |
27 | **Sukko**
28 |
29 | - Do not change spelling
30 | - First letter should always be capitalized
31 |
32 | ### Logo
33 |
34 | Do not modify colors or symbols.
35 |
36 |
37 |
38 |
39 |
40 | ### Screenshots
41 |
42 | Can be slightly outdated, but they represent core functionality and main screens.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyAndBoolean.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.AndNode
5 | import io.github.sadellie.sukko.core.script.BoolNode
6 | import io.github.sadellie.sukko.core.script.FalseNode
7 | import io.github.sadellie.sukko.core.script.ScriptContext
8 | import io.github.sadellie.sukko.core.script.TrueNode
9 |
10 | internal val SimplifyAndBoolean =
11 | object : SimplificationRule {
12 | override suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep? =
13 | simplifyBottomToTop(SimplificationType.LOGICAL_AND, context, tree) { currentNode ->
14 | // only work with logical and node. skip to child and move to neighbour
15 | if (currentNode !is AndNode) return@simplifyBottomToTop null
16 |
17 | // find booleans in this node
18 | val left =
19 | currentNode.getParameter(0)?.toBoolean() ?: return@simplifyBottomToTop null
20 | val right =
21 | currentNode.getParameter(1)?.toBoolean() ?: return@simplifyBottomToTop null
22 | // kill parent node and return result as boolean node
23 | return@simplifyBottomToTop if (left && right) TrueNode else FalseNode
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/designsystem/src/commonMain/kotlin/io/github/sadellie/sukko/core/designsystem/Preview2.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.designsystem
2 |
3 | import androidx.compose.material3.LocalContentColor
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Surface
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.CompositionLocalProvider
8 | import androidx.compose.ui.graphics.toArgb
9 | import coil3.ColorImage
10 | import coil3.ImageLoader
11 | import coil3.annotation.ExperimentalCoilApi
12 | import coil3.compose.AsyncImagePreviewHandler
13 | import coil3.compose.LocalAsyncImagePreviewHandler
14 | import coil3.compose.LocalPlatformContext
15 | import okio.Path.Companion.toPath
16 |
17 | @OptIn(ExperimentalCoilApi::class)
18 | @Composable
19 | fun Preview2(content: @Composable () -> Unit) {
20 | val placeholderColor = MaterialTheme.colorScheme.tertiary
21 | val previewHandler = AsyncImagePreviewHandler { ColorImage(placeholderColor.toArgb()) }
22 |
23 | CompositionLocalProvider(
24 | LocalAsyncImagePreviewHandler provides previewHandler,
25 | LocalImageLoader provides ImageLoader.Builder(LocalPlatformContext.current).build(),
26 | LocalFilesDirPath provides "".toPath(),
27 | LocalContentColor provides MaterialTheme.colorScheme.onSurface,
28 | ) {
29 | Surface(content = content)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/feature/presetselector/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets.commonMain.dependencies {
10 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
11 | implementation(compose.components.resources)
12 | implementation(compose.components.uiToolingPreview)
13 | implementation(compose.ui)
14 | implementation(libs.androidx.navigation3.navigation3.runtime)
15 | implementation(libs.com.composables.core)
16 | implementation(libs.com.squareup.okio.okio)
17 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
18 | implementation(libs.org.jetbrains.compose.material3.material3)
19 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
20 | implementation(project(":core:common"))
21 | implementation(project(":core:data"))
22 | implementation(project(":core:designsystem"))
23 | implementation(project(":core:model"))
24 | implementation(project(":core:ui"))
25 | implementation(project(":material-symbols"))
26 | }
27 | sourceSets.commonTest.dependencies {}
28 | }
29 |
30 | compose.resources.generateResClass = compose.resources.never
31 |
32 | android { namespace = "io.github.sadellie.sukko.feature.presetselector" }
33 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyOrBooleanTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.OrNode
5 | import io.github.sadellie.sukko.core.script.ScriptException
6 | import io.github.sadellie.sukko.core.script.TrueNode
7 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
8 | import kotlin.test.Test
9 | import kotlin.test.assertFailsWith
10 |
11 | class SimplifyOrBooleanTest {
12 | @Test fun simplify_logicalOr1() = assertSimplify("true || true", TrueNode)
13 |
14 | @Test fun simplify_logicalOr2() = assertSimplify("true || false", TrueNode)
15 |
16 | @Test
17 | fun simplify_logicalOr3() = assertSimplify("true || (false || true)", OrNode(TrueNode, TrueNode))
18 |
19 | @Test fun simplify_logicalOr4() = assertSimplify("true || (123 + 456)", null)
20 |
21 | @Test
22 | fun simplify_logicalOr5() {
23 | assertFailsWith { assertSimplify("true || 123", null) }
24 | }
25 |
26 | private fun assertSimplify(input: String, expected: ASTNode?) {
27 | assertSimplifySingleRule(
28 | rule = SimplifyOrBoolean,
29 | simplificationType = SimplificationType.LOGICAL_OR,
30 | input = input,
31 | expected = expected,
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/SizeModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.ScriptableDp
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_size
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdSizeModifier(
17 | override val id: Int,
18 | val size: ScriptableDp = ScriptableDp.Fixed(48.dp),
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_size
21 |
22 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
23 | EvaluatedSizeModifier(id = id, size = size.getValue(layerContext, globals))
24 |
25 | override fun updateId(newId: Int) = this.copy(id = newId)
26 | }
27 |
28 | data class EvaluatedSizeModifier(override val id: Int, val size: Dp) : WidgetModifier.Evaluated {
29 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.size(size = size)
30 | }
31 |
--------------------------------------------------------------------------------
/core/model/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets.commonMain.dependencies {
10 | implementation(compose.components.resources)
11 | implementation(compose.components.uiToolingPreview)
12 | implementation(compose.foundation)
13 | implementation(libs.co.touchlab.kermit)
14 | implementation(libs.com.squareup.okio.okio)
15 | implementation(libs.io.coil.kt.coil3.coil.compose)
16 | implementation(libs.org.jetbrains.compose.material3.material3)
17 | implementation(libs.org.jetbrains.kotlinx.kotlinx.datetime)
18 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
19 | implementation(project(":core:common"))
20 | implementation(project(":core:designsystem"))
21 | implementation(project(":core:fontfiles"))
22 | implementation(project(":core:iconfiles"))
23 | implementation(project(":core:script"))
24 | implementation(project(":material-symbols"))
25 | }
26 | sourceSets.commonTest.dependencies {
27 | implementation(libs.kotlin.test)
28 | implementation(libs.org.jetbrains.kotlinx.kotlinx.coroutines.test)
29 | }
30 | }
31 |
32 | compose.resources.generateResClass = compose.resources.never
33 |
34 | android { namespace = "io.github.sadellie.sukko.core.model" }
35 |
--------------------------------------------------------------------------------
/feature/editor/src/wasmJsMain/kotlin/io/github/sadellie/sukko/feature/editor/EditorScene.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import io.github.sadellie.sukko.core.common.collectAsStateWithLifecycleKMP
6 | import io.github.sadellie.sukko.core.ui.LoadingScaffoldWithTopAppBar
7 |
8 | @Composable
9 | actual fun EditorScene(
10 | onNavigateUp: () -> Unit,
11 | onNavigateToSaveAsPreset: () -> Unit,
12 | onNavigateToPresetSelector: () -> Unit,
13 | viewModel: EditorViewModel,
14 | ) {
15 | LaunchedEffect(Unit) { viewModel.onUpdateWidgetSize() }
16 |
17 | when (val uiState = viewModel.uiState.collectAsStateWithLifecycleKMP().value) {
18 | null -> LoadingScaffoldWithTopAppBar(onNavigateUp = onNavigateUp, disableBack = false)
19 | else ->
20 | EditorScreen(
21 | uiState = uiState,
22 | onNavigateUp = onNavigateUp,
23 | onSave = { _, _ -> },
24 | onRename = viewModel::renameWidget,
25 | onNavigateToSaveAsPreset = onNavigateToSaveAsPreset,
26 | onNavigateToLayer = viewModel::onNavigateToLayer,
27 | onEvent = viewModel::onEvent,
28 | onUpdateWidgetDataSaverState = viewModel::updateWidgetSaverState,
29 | onNavigateNotificationListener = {},
30 | onNavigateToPresetSelector = onNavigateToPresetSelector,
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/feature/saveaspreset/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.multiplatform.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets.commonMain.dependencies {
10 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
11 | implementation(compose.components.resources)
12 | implementation(compose.components.uiToolingPreview)
13 | implementation(compose.ui)
14 | implementation(libs.androidx.navigation3.navigation3.runtime)
15 | implementation(libs.co.touchlab.kermit)
16 | implementation(libs.com.composables.core)
17 | implementation(libs.com.squareup.okio.okio)
18 | implementation(libs.io.coil.kt.coil3.coil.compose)
19 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
20 | implementation(libs.org.jetbrains.compose.material3.material3)
21 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
22 | implementation(project(":core:common"))
23 | implementation(project(":core:data"))
24 | implementation(project(":core:designsystem"))
25 | implementation(project(":core:model"))
26 | implementation(project(":core:ui"))
27 | implementation(project(":material-symbols"))
28 | }
29 | }
30 |
31 | compose.resources.generateResClass = compose.resources.never
32 |
33 | android { namespace = "io.github.sadellie.sukko.feature.saveaspreset" }
34 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/io/github/sadellie/sukko/core/database/WidgetDataPresetBased.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.database
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Dao
5 | import androidx.room.Entity
6 | import androidx.room.Insert
7 | import androidx.room.OnConflictStrategy
8 | import androidx.room.PrimaryKey
9 | import androidx.room.Query
10 | import kotlinx.coroutines.flow.Flow
11 |
12 | @Entity(tableName = "widget_data_preset")
13 | data class WidgetDataPresetBased(
14 | @PrimaryKey(autoGenerate = true) @ColumnInfo("presetId") val presetId: Long,
15 | @ColumnInfo("name") val name: String,
16 | @ColumnInfo("layers") val layers: String,
17 | @ColumnInfo("globals") val globals: String,
18 | )
19 |
20 | @Dao
21 | interface WidgetDataPresetDao {
22 | @Query("SELECT * FROM widget_data_preset") fun getAll(): Flow>
23 |
24 | @Query("SELECT * FROM widget_data_preset WHERE presetId = :presetId LIMIT 1")
25 | suspend fun getById(presetId: Long): WidgetDataPresetBased?
26 |
27 | @Insert(onConflict = OnConflictStrategy.ABORT)
28 | suspend fun insertNew(widgetDataPresetBased: WidgetDataPresetBased): Long
29 |
30 | @Query("UPDATE widget_data_preset SET name = :newName WHERE presetId = :presetId")
31 | suspend fun rename(presetId: Long, newName: String)
32 |
33 | @Query("DELETE FROM widget_data_preset WHERE presetId = :presetId")
34 | suspend fun deleteById(presetId: Long)
35 | }
36 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/ASTNode.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script
2 |
3 | import io.github.sadellie.sukko.core.script.token.Token3
4 |
5 | internal sealed interface ASTNode {
6 | val token: Token3
7 | val children: List
8 |
9 | /**
10 | * Collapse a tree to prepare it for simplifications. While collapsing and simplification do same
11 | * thing, collapsing is considered to be a separate step to ensure all simplifications receive
12 | * stable and expected tree.
13 | */
14 | fun collapse(scriptContext: ScriptContext): ASTNode {
15 | val collapsedChildren = children.map { it.collapse(scriptContext) }
16 | val updatedNode = this.withNewChildren(collapsedChildren)
17 | return updatedNode
18 | }
19 |
20 | /** Creates a copy with provided [children]. */
21 | fun withNewChildren(children: List): ASTNode
22 |
23 | /** Format string for displaying in expression */
24 | fun toFormattedString(): String
25 |
26 | /** Format string for displaying final result. No quotes and similar symbols */
27 | fun toFinalFormattedString(): String = toFormattedString()
28 |
29 | companion object {
30 | internal fun buildTrees(tokens: List) = ASTBuilder(tokens).build()
31 |
32 | internal fun buildTreesAndCollapse(tokens: List, scriptContext: ScriptContext) =
33 | buildTrees(tokens).map { it.collapse(scriptContext) }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyAndBooleanTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.AndNode
5 | import io.github.sadellie.sukko.core.script.FalseNode
6 | import io.github.sadellie.sukko.core.script.ScriptException
7 | import io.github.sadellie.sukko.core.script.TrueNode
8 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
9 | import kotlin.test.Test
10 | import kotlin.test.assertFailsWith
11 |
12 | class SimplifyAndBooleanTest {
13 | @Test fun simplify_logicalOr1() = assertSimplify("true && true", TrueNode)
14 |
15 | @Test fun simplify_logicalOr2() = assertSimplify("true && false", FalseNode)
16 |
17 | @Test
18 | fun simplify_logicalOr3() =
19 | assertSimplify("true && (false && true)", AndNode(TrueNode, FalseNode))
20 |
21 | @Test fun simplify_logicalOr4() = assertSimplify("true && (123 + 456)", null)
22 |
23 | @Test
24 | fun simplify_logicalOr5() {
25 | assertFailsWith { assertSimplify("true && 123", null) }
26 | }
27 |
28 | private fun assertSimplify(input: String, expected: ASTNode?) {
29 | assertSimplifySingleRule(
30 | rule = SimplifyAndBoolean,
31 | simplificationType = SimplificationType.LOGICAL_AND,
32 | input = input,
33 | expected = expected,
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/feature/fontseditor/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | android { namespace = "io.github.sadellie.sukko.feature.fontseditor" }
9 |
10 | dependencies {
11 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
12 | implementation(compose.components.resources)
13 | implementation(compose.components.uiToolingPreview)
14 | implementation(compose.ui)
15 | implementation(libs.androidx.core.ktx)
16 | implementation(libs.androidx.navigation3.navigation3.runtime)
17 | implementation(libs.co.touchlab.kermit)
18 | implementation(libs.com.composables.core)
19 | implementation(libs.com.squareup.okio.okio)
20 | implementation(libs.io.github.vinceglb.filekit.dialogs.compose)
21 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
22 | implementation(libs.org.jetbrains.compose.material3.material3)
23 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
24 | implementation(project(":core:common"))
25 | implementation(project(":core:data"))
26 | implementation(project(":core:designsystem"))
27 | implementation(project(":core:fontfiles"))
28 | implementation(project(":core:model"))
29 | implementation(project(":core:ui"))
30 | implementation(project(":material-symbols"))
31 | }
32 |
33 | compose.resources.generateResClass = compose.resources.never
34 |
--------------------------------------------------------------------------------
/core/data/src/androidMain/kotlin/io/github/sadellie/sukko/core/data/InstalledAppsProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.compose.ui.graphics.asImageBitmap
6 | import androidx.core.graphics.drawable.toBitmap
7 | import io.github.sadellie.sukko.core.model.InstalledApp
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 |
11 | class InstalledAppsProviderImpl(private val context: Context) : InstalledAppsProvider {
12 | override suspend fun getAllApps(): List =
13 | withContext(Dispatchers.Default) {
14 | val packageManager = context.packageManager
15 | val mainIntent =
16 | Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
17 |
18 | val intentActivities = packageManager.queryIntentActivities(mainIntent, 0)
19 | val allApps = mutableSetOf()
20 | for (resolveInfo in intentActivities) {
21 | val label = resolveInfo.loadLabel(packageManager).toString()
22 | val packageId = resolveInfo.activityInfo?.packageName ?: continue
23 | val icon = resolveInfo.loadIcon(packageManager).toBitmap().asImageBitmap()
24 | allApps.add(InstalledApp(label, packageId, icon))
25 | }
26 |
27 | val result = allApps.distinctBy { it.packageId }.sortedBy { it.label }
28 | return@withContext result
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/themmo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
2 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
3 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
4 |
5 | plugins {
6 | // decouple from main app for multiplatform unitto
7 | alias(libs.plugins.android.library)
8 | alias(libs.plugins.compose)
9 | alias(libs.plugins.compose.compiler)
10 | alias(libs.plugins.multiplatform)
11 | }
12 |
13 | kotlin {
14 | androidTarget {
15 | @OptIn(ExperimentalKotlinGradlePluginApi::class)
16 | compilerOptions { jvmTarget.set(JvmTarget.JVM_11) }
17 | }
18 | @OptIn(ExperimentalWasmDsl::class)
19 | wasmJs {
20 | outputModuleName.set("composeApp")
21 | browser()
22 | binaries.executable()
23 | }
24 |
25 | sourceSets.commonMain.dependencies {
26 | implementation(compose.components.resources)
27 | implementation(compose.components.uiToolingPreview)
28 | implementation(compose.foundation)
29 | implementation(libs.com.materialkolor.material.color.utilities)
30 | implementation(libs.org.jetbrains.compose.material3.material3)
31 | }
32 | compilerOptions { optIn.addAll("androidx.compose.material3.ExperimentalMaterial3ExpressiveApi") }
33 | }
34 |
35 | compose.resources.generateResClass = compose.resources.never
36 |
37 | android {
38 | namespace = "io.github.sadellie.themmo"
39 | defaultConfig.minSdk = 31
40 | defaultConfig.targetSdk = 36
41 | compileSdk = defaultConfig.targetSdk
42 | }
43 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyDivideNumbers.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.DivideNode
5 | import io.github.sadellie.sukko.core.script.NumberNode
6 | import io.github.sadellie.sukko.core.script.ScriptContext
7 |
8 | /**
9 | * - 6 / 2 = 3
10 | * - 5 / 2 = 2.5
11 | * - 5 / 2 * 3 = 2.5 * 3
12 | */
13 | internal val SimplifyDivideNumbers =
14 | object : SimplificationRule {
15 | override suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep? =
16 | simplifyBottomToTop(SimplificationType.DIVISION_OF_NUMBERS, context, tree) { currentNode ->
17 | // only work with divide node. skip to child and move to neighbour
18 | if (currentNode !is DivideNode) return@simplifyBottomToTop null
19 |
20 | // find numbers in this divide node
21 | val numberNodes = currentNode.children.filterIsInstance()
22 | if (numberNodes.size != 2) return@simplifyBottomToTop null
23 | // divide found number nodes
24 | val divisionOfNumberNodes = numberNodes[0].toDouble() / numberNodes[1].toDouble()
25 | val divisionAsNumberNode = NumberNode(divisionOfNumberNodes)
26 |
27 | // kill parent division node and return result as number node
28 | return@simplifyBottomToTop divisionAsNumberNode
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyEqualCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyEqualCheckTest {
11 | @Test fun simplify_equal1() = assertSimplify("2 == 2", TrueNode)
12 |
13 | @Test fun simplify_equal2() = assertSimplify("1 == 2", FalseNode)
14 |
15 | @Test fun simplify_equal3() = assertSimplify("\"2\" == 2", null)
16 |
17 | @Test fun simplify_equal4() = assertSimplify("(2 * 3) == 6", null)
18 |
19 | @Test fun simplify_equal5() = assertSimplify("(2 * 3) == (2 * 3)", null)
20 |
21 | @Test fun simplify_equal6() = assertSimplify("true == true", TrueNode)
22 |
23 | @Test fun simplify_equal7() = assertSimplify("false == false", TrueNode)
24 |
25 | @Test fun simplify_equal8() = assertSimplify("true == false", FalseNode)
26 |
27 | private fun assertSimplify(input: String, expected: ASTNode?) {
28 | assertSimplifySingleRule(
29 | rule = SimplifyEqualCheck,
30 | simplificationType = SimplificationType.EQUAL,
31 | input = input,
32 | expected = expected,
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplificationRule.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.ScriptContext
5 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyEqualCheck
6 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyGreaterCheck
7 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyGreaterOrEqualCheck
8 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyLessCheck
9 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyLessOrEqualCheck
10 | import io.github.sadellie.sukko.core.script.simplify.equality.SimplifyNotEqualCheck
11 |
12 | internal interface SimplificationRule {
13 | suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep?
14 |
15 | companion object {
16 | fun allRules(): List =
17 | listOf(
18 | SimplifyConstant,
19 | SimplifyOrBoolean,
20 | SimplifyAndBoolean,
21 | SimplifyDivideNumbers,
22 | SimplifyEqualCheck,
23 | SimplifyFunction,
24 | SimplifyGreaterCheck,
25 | SimplifyGreaterOrEqualCheck,
26 | SimplifyLessCheck,
27 | SimplifyLessOrEqualCheck,
28 | SimplifyMultiplyNumbers,
29 | SimplifyNotEqualCheck,
30 | SimplifyPlusNumbers,
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/AlphaModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.draw.alpha
5 | import io.github.sadellie.sukko.core.model.Globals
6 | import io.github.sadellie.sukko.core.model.LayerContext
7 | import io.github.sadellie.sukko.core.model.basic.ScriptableDouble
8 | import io.github.sadellie.sukko.resources.Res
9 | import io.github.sadellie.sukko.resources.core_model_modifier_alpha
10 | import kotlinx.serialization.Serializable
11 | import kotlinx.serialization.Transient
12 |
13 | @Serializable
14 | data class ColdAlphaModifier(
15 | override val id: Int,
16 | val alpha: ScriptableDouble = ScriptableDouble.Fixed(1.0),
17 | ) : WidgetModifier.Cold {
18 | @Transient override val displayName = Res.string.core_model_modifier_alpha
19 |
20 | companion object {
21 | val alphaRange by lazy { 0.0..1.0 }
22 | }
23 |
24 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
25 | EvaluatedAlphaModifier(
26 | id = id,
27 | alpha = alpha.getValue(layerContext, globals).coerceIn(alphaRange).toFloat(),
28 | )
29 |
30 | override fun updateId(newId: Int): WidgetModifier.Cold = this.copy(id = newId)
31 | }
32 |
33 | data class EvaluatedAlphaModifier(override val id: Int, val alpha: Float) :
34 | WidgetModifier.Evaluated {
35 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.alpha(alpha)
36 | }
37 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyNotEqualCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyNotEqualCheckTest {
11 | @Test fun simplify_notEqual1() = assertSimplify("2 != 2", FalseNode)
12 |
13 | @Test fun simplify_notEqual2() = assertSimplify("1 != 2", TrueNode)
14 |
15 | @Test fun simplify_notEqual3() = assertSimplify("\"2\" != 2", null)
16 |
17 | @Test fun simplify_notEqual4() = assertSimplify("(2 * 3) != 6", null)
18 |
19 | @Test fun simplify_notEqual5() = assertSimplify("(2 * 3) != (2 * 3)", null)
20 |
21 | @Test fun simplify_notEqual6() = assertSimplify("true != true", FalseNode)
22 |
23 | @Test fun simplify_notEqual7() = assertSimplify("false != false", FalseNode)
24 |
25 | @Test fun simplify_notEqual8() = assertSimplify("true != false", TrueNode)
26 |
27 | private fun assertSimplify(input: String, expected: ASTNode?) {
28 | assertSimplifySingleRule(
29 | rule = SimplifyNotEqualCheck,
30 | simplificationType = SimplificationType.NOT_EQUAL,
31 | input = input,
32 | expected = expected,
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/data/src/androidUnitTest/kotlin/io/github/sadellie/sukko/core/data/DynamicColorSchemeProviderImplTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import android.graphics.BitmapFactory
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.graphics.asImageBitmap
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import io.github.sadellie.sukko.core.common.toHex
8 | import kotlin.test.Test
9 | import kotlin.test.assertEquals
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.test.runTest
12 | import kotlinx.coroutines.withContext
13 | import okio.Path.Companion.toPath
14 | import org.junit.runner.RunWith
15 |
16 | @RunWith(AndroidJUnit4::class)
17 | class DynamicColorSchemeProviderImplTest {
18 | @Test
19 | fun generateColorSchemeFromImage_validPng() = runTest {
20 | val sourceImagePath = "./testFiles/file.png".toPath()
21 | val sourceFile = sourceImagePath.toFile()
22 | if (!sourceFile.exists()) error("Not found: ${sourceFile.absolutePath}")
23 | withContext(Dispatchers.IO) {
24 | val imageBitmap = BitmapFactory.decodeFile(sourceFile.absolutePath).asImageBitmap()
25 | val colorScheme = generateColorSchemeFromImage(imageBitmap)
26 | val expectedPrimary = Color(0xFF6E6600)
27 | val actualPrimary = colorScheme.primary
28 | assertEquals(
29 | expectedPrimary,
30 | actualPrimary,
31 | "Expected: ${expectedPrimary.toHex()} Actual: ${actualPrimary.toHex()}",
32 | )
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/feature/icopackeditor/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | android { namespace = "io.github.sadellie.sukko.feature.icopackeditor" }
9 |
10 | dependencies {
11 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
12 | implementation(compose.components.resources)
13 | implementation(compose.components.uiToolingPreview)
14 | implementation(compose.ui)
15 | implementation(libs.androidx.core.ktx)
16 | implementation(libs.androidx.navigation3.navigation3.runtime)
17 | implementation(libs.co.touchlab.kermit)
18 | implementation(libs.com.composables.core)
19 | implementation(libs.com.squareup.okio.okio)
20 | implementation(libs.io.coil.kt.coil3.coil.compose)
21 | implementation(libs.io.github.vinceglb.filekit.dialogs.compose)
22 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
23 | implementation(libs.org.jetbrains.compose.material3.material3)
24 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
25 | implementation(project(":core:common"))
26 | implementation(project(":core:data"))
27 | implementation(project(":core:designsystem"))
28 | implementation(project(":core:iconfiles"))
29 | implementation(project(":core:model"))
30 | implementation(project(":core:ui"))
31 | implementation(project(":material-symbols"))
32 | }
33 |
34 | compose.resources.generateResClass = compose.resources.never
35 |
--------------------------------------------------------------------------------
/core/data/src/androidMain/kotlin/io/github/sadellie/sukko/core/data/BatteryInfoProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import android.content.Context
4 | import android.os.BatteryManager
5 | import android.os.PowerManager
6 | import io.github.sadellie.sukko.core.model.data.BatteryInfoProvider
7 | import kotlin.time.Duration.Companion.milliseconds
8 |
9 | class BatteryInfoProviderImpl(context: Context) : BatteryInfoProvider {
10 | private val batteryManager by lazy {
11 | context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
12 | }
13 |
14 | override val capacity: Int by lazy {
15 | batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
16 | }
17 |
18 | override val status: String by lazy {
19 | when (batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)) {
20 | BatteryManager.BATTERY_STATUS_NOT_CHARGING -> "NOT_CHARGING"
21 | BatteryManager.BATTERY_STATUS_DISCHARGING -> "DISCHARGING"
22 | BatteryManager.BATTERY_STATUS_CHARGING -> "CHARGING"
23 | BatteryManager.BATTERY_STATUS_FULL -> "FULL"
24 | else -> "UNKNOWN"
25 | }
26 | }
27 |
28 | override val chargeDischargeSeconds by lazy {
29 | val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
30 | val chargeInSeconds =
31 | powerManager.batteryDischargePrediction?.toSeconds()
32 | ?: batteryManager.computeChargeTimeRemaining().milliseconds.inWholeSeconds
33 | chargeInSeconds.toInt()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierClip.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.modifier.ColdClipModifier
9 | import io.github.sadellie.sukko.core.ui.expand
10 | import io.github.sadellie.sukko.feature.editor.selector.ShapeSelectorSheet
11 | import org.jetbrains.compose.resources.stringResource
12 | import sh.calvin.reorderable.ReorderableCollectionItemScope
13 |
14 | @Composable
15 | internal fun ReorderableCollectionItemScope.EditorModifierClip(
16 | modifier: Modifier,
17 | widgetModifier: ColdClipModifier,
18 | onUpdateModifier: (ColdClipModifier) -> Unit,
19 | state: EditorModifierListItemState,
20 | ) {
21 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
22 | EditorModifierFlatListItem(
23 | modifier = modifier.clickable { sheetState.expand() },
24 | headlineText = stringResource(widgetModifier.displayName),
25 | supportingText = stringResource(widgetModifier.shapeSource.displayName),
26 | state = state,
27 | )
28 |
29 | ShapeSelectorSheet(
30 | state = sheetState,
31 | onValueSelected = { onUpdateModifier(widgetModifier.copy(shapeSource = it)) },
32 | value = widgetModifier.shapeSource,
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyLessCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyLessCheckTest {
11 | @Test fun simplify_less1() = assertSimplify("2 < 2", FalseNode)
12 |
13 | @Test fun simplify_less2() = assertSimplify("1 < 2", TrueNode)
14 |
15 | @Test fun simplify_less3() = assertSimplify("\"2\" < 2", null)
16 |
17 | @Test fun simplify_less4() = assertSimplify("(2 * 3) < 6", null)
18 |
19 | @Test fun simplify_less5() = assertSimplify("(2 * 3) < (2 * 3)", null)
20 |
21 | @Test fun simplify_less6() = assertSimplify("true < true", FalseNode)
22 |
23 | @Test fun simplify_less7() = assertSimplify("false < false", FalseNode)
24 |
25 | @Test fun simplify_less8() = assertSimplify("false < true", TrueNode)
26 |
27 | @Test fun simplify_less9() = assertSimplify("true < false", FalseNode)
28 |
29 | private fun assertSimplify(input: String, expected: ASTNode?) {
30 | assertSimplifySingleRule(
31 | rule = SimplifyLessCheck,
32 | simplificationType = SimplificationType.LESS,
33 | input = input,
34 | expected = expected,
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/OffsetModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.offset
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.ScriptableDp
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_offset
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdOffsetModifier(
17 | override val id: Int,
18 | val x: ScriptableDp = ScriptableDp.Fixed(0.dp),
19 | val y: ScriptableDp = ScriptableDp.Fixed(0.dp),
20 | ) : WidgetModifier.Cold {
21 | @Transient override val displayName = Res.string.core_model_modifier_offset
22 |
23 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
24 | EvaluatedOffsetModifier(
25 | id = id,
26 | x = x.getValue(layerContext, globals),
27 | y = y.getValue(layerContext, globals),
28 | )
29 |
30 | override fun updateId(newId: Int) = this.copy(id = newId)
31 | }
32 |
33 | data class EvaluatedOffsetModifier(override val id: Int, val x: Dp, val y: Dp) :
34 | WidgetModifier.Evaluated {
35 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.offset(x, y)
36 | }
37 |
--------------------------------------------------------------------------------
/core/common/src/commonTest/kotlin/io/github/sadellie/sukko/core/common/ColorUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import kotlin.test.Test
5 | import kotlin.test.assertEquals
6 |
7 | class ColorUtilsTest {
8 | @Test
9 | fun toHex_testWithAlpha() {
10 | val input = Color(0xAB123456)
11 | val expected = "AB123456"
12 | val actual = input.toHex()
13 | assertEquals(expected, actual)
14 | }
15 |
16 | @Test
17 | fun toHex_testWithoutAlpha() {
18 | val input = Color(0xFF123456)
19 | val expected = "123456"
20 | val actual = input.toHex()
21 | assertEquals(expected, actual)
22 | }
23 |
24 | @Test
25 | fun toHex_testUnspecified() {
26 | val input = Color.Unspecified
27 | val expected = "00000000"
28 | val actual = input.toHex()
29 | assertEquals(expected, actual)
30 | }
31 |
32 | @Test
33 | fun hexToColor_testWithAlpha() {
34 | val input = "ab123456"
35 | val expected = Color(0xAB123456)
36 | val actual = input.hexToColor()
37 | assertEquals(expected, actual)
38 | }
39 |
40 | @Test
41 | fun hexToColor_testWithoutAlpha() {
42 | val input = "123456"
43 | val expected = Color(0xFF123456)
44 | val actual = input.hexToColor()
45 | assertEquals(expected, actual)
46 | }
47 |
48 | @Test
49 | fun hexToColor_testWithoutAlphaWithHashtag() {
50 | val input = "#123456"
51 | val expected = Color(0xFF123456)
52 | val actual = input.hexToColor()
53 | assertEquals(expected, actual)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/feature/settings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | compose.resources.generateResClass = compose.resources.never
9 |
10 | android { namespace = "io.github.sadellie.sukko.feature.settings" }
11 |
12 | dependencies {
13 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
14 | implementation(compose.components.resources)
15 | implementation(compose.components.uiToolingPreview)
16 | implementation(compose.ui)
17 | implementation(libs.androidx.core.ktx)
18 | implementation(libs.androidx.navigation3.navigation3.runtime)
19 | implementation(libs.co.touchlab.kermit)
20 | implementation(libs.com.composables.core)
21 | implementation(libs.com.squareup.okio.okio)
22 | implementation(libs.io.coil.kt.coil3.coil.compose)
23 | implementation(libs.io.github.vinceglb.filekit.dialogs.compose)
24 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
25 | implementation(libs.org.jetbrains.compose.material3.material3)
26 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
27 | implementation(project(":core:common"))
28 | implementation(project(":core:data"))
29 | implementation(project(":core:designsystem"))
30 | implementation(project(":core:iconfiles"))
31 | implementation(project(":core:medialistener"))
32 | implementation(project(":core:model"))
33 | implementation(project(":core:ui"))
34 | implementation(project(":material-symbols"))
35 | }
36 |
--------------------------------------------------------------------------------
/core/widget/src/androidMain/kotlin/io/github/sadellie/sukko/core/widget/WidgetInfoRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.widget
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import androidx.compose.ui.unit.DpSize
7 | import io.github.sadellie.sukko.core.common.appWidgetSize
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.delay
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.distinctUntilChanged
12 | import kotlinx.coroutines.flow.flow
13 | import kotlinx.coroutines.flow.flowOn
14 | import kotlinx.coroutines.withContext
15 |
16 | class WidgetInfoRepositoryImpl(private val context: Context) : WidgetInfoRepository {
17 | private val appWidgetManager = AppWidgetManager.getInstance(context)
18 | private val componentName = ComponentName(context, MainWidgetProvider::class.java)
19 |
20 | override fun allWidgetIds(): Flow =
21 | flow {
22 | while (true) {
23 | val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
24 | emit(widgetIds)
25 | delay(WIDGET_INFO_UPDATE_RATE_MS)
26 | }
27 | }
28 | .distinctUntilChanged()
29 | .flowOn(Dispatchers.Default)
30 |
31 | override suspend fun getWidgetSize(appWidgetId: Int): DpSize =
32 | withContext(Dispatchers.Default) {
33 | appWidgetManager.appWidgetSize(appWidgetId, context.resources.configuration.orientation)
34 | }
35 | }
36 |
37 | private const val WIDGET_INFO_UPDATE_RATE_MS = 5_000L
38 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/WidthModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.width
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.ScriptableDp
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_width
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdWidthModifier(
17 | override val id: Int,
18 | val width: ScriptableDp = ScriptableDp.Fixed(72.dp),
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_width
21 |
22 | companion object {
23 | val widthRange by lazy { 0.dp..Dp.Infinity }
24 | }
25 |
26 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
27 | EvaluatedWidthModifier(
28 | id = id,
29 | width = width.getValue(layerContext, globals).coerceIn(widthRange),
30 | )
31 |
32 | override fun updateId(newId: Int): WidgetModifier.Cold = this.copy(id = newId)
33 | }
34 |
35 | data class EvaluatedWidthModifier(override val id: Int, val width: Dp) : WidgetModifier.Evaluated {
36 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.width(width)
37 | }
38 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyGreaterCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyGreaterCheckTest {
11 | @Test fun simplify_greater1() = assertSimplify("2 > 2", FalseNode)
12 |
13 | @Test fun simplify_greater2() = assertSimplify("1 > 2", FalseNode)
14 |
15 | @Test fun simplify_greater3() = assertSimplify("\"2\" > 2", null)
16 |
17 | @Test fun simplify_greater4() = assertSimplify("(2 * 3) > 6", null)
18 |
19 | @Test fun simplify_greater5() = assertSimplify("(2 * 3) > (2 * 3)", null)
20 |
21 | @Test fun simplify_greater6() = assertSimplify("true > true", FalseNode)
22 |
23 | @Test fun simplify_greater7() = assertSimplify("false > false", FalseNode)
24 |
25 | @Test fun simplify_greater8() = assertSimplify("true > false", TrueNode)
26 |
27 | @Test fun simplify_greater9() = assertSimplify("false > true", FalseNode)
28 |
29 | private fun assertSimplify(input: String, expected: ASTNode?) {
30 | assertSimplifySingleRule(
31 | rule = SimplifyGreaterCheck,
32 | simplificationType = SimplificationType.GREATER,
33 | input = input,
34 | expected = expected,
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/FillMaxSizeModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.ui.Modifier
5 | import io.github.sadellie.sukko.core.model.Globals
6 | import io.github.sadellie.sukko.core.model.LayerContext
7 | import io.github.sadellie.sukko.core.model.basic.ScriptableDouble
8 | import io.github.sadellie.sukko.resources.Res
9 | import io.github.sadellie.sukko.resources.core_model_modifier_fill_max_size
10 | import kotlinx.serialization.Serializable
11 | import kotlinx.serialization.Transient
12 |
13 | @Serializable
14 | data class ColdFillMaxSizeModifier(
15 | override val id: Int,
16 | val fraction: ScriptableDouble = ScriptableDouble.Fixed(1.0),
17 | ) : WidgetModifier.Cold {
18 | @Transient override val displayName = Res.string.core_model_modifier_fill_max_size
19 |
20 | companion object {
21 | val fractionRange by lazy { 0.0..1.0 }
22 | }
23 |
24 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
25 | EvaluatedFillMaxSizeModifier(
26 | id = id,
27 | fraction = fraction.getValue(layerContext, globals).coerceIn(fractionRange).toFloat(),
28 | )
29 |
30 | override fun updateId(newId: Int) = this.copy(id = newId)
31 | }
32 |
33 | data class EvaluatedFillMaxSizeModifier(override val id: Int, val fraction: Float) :
34 | WidgetModifier.Evaluated {
35 | override fun addToModifier(modifier: Modifier, scope: Any) =
36 | modifier.fillMaxSize(fraction = fraction)
37 | }
38 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/FillMaxWidthModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.ui.Modifier
5 | import io.github.sadellie.sukko.core.model.Globals
6 | import io.github.sadellie.sukko.core.model.LayerContext
7 | import io.github.sadellie.sukko.core.model.basic.ScriptableDouble
8 | import io.github.sadellie.sukko.resources.Res
9 | import io.github.sadellie.sukko.resources.core_model_modifier_fill_max_width
10 | import kotlinx.serialization.Serializable
11 | import kotlinx.serialization.Transient
12 |
13 | @Serializable
14 | data class ColdFillMaxWidthModifier(
15 | override val id: Int,
16 | val fraction: ScriptableDouble = ScriptableDouble.Fixed(1.0),
17 | ) : WidgetModifier.Cold {
18 | @Transient override val displayName = Res.string.core_model_modifier_fill_max_width
19 |
20 | companion object {
21 | val fractionRange by lazy { 0.0..1.0 }
22 | }
23 |
24 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
25 | EvaluatedFillMaxWidthModifier(
26 | id = id,
27 | fraction = fraction.getValue(layerContext, globals).coerceIn(fractionRange).toFloat(),
28 | )
29 |
30 | override fun updateId(newId: Int) = this.copy(id = newId)
31 | }
32 |
33 | data class EvaluatedFillMaxWidthModifier(override val id: Int, val fraction: Float) :
34 | WidgetModifier.Evaluated {
35 | override fun addToModifier(modifier: Modifier, scope: Any) =
36 | modifier.fillMaxWidth(fraction = fraction)
37 | }
38 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/HeightModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.height
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.ScriptableDp
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_height
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdHeightModifier(
17 | override val id: Int,
18 | val height: ScriptableDp = ScriptableDp.Fixed(72.dp),
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_height
21 |
22 | companion object {
23 | val heightRange by lazy { 0.dp..Dp.Infinity }
24 | }
25 |
26 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
27 | EvaluatedHeightModifier(
28 | id = id,
29 | height = height.getValue(layerContext, globals).coerceIn(heightRange),
30 | )
31 |
32 | override fun updateId(newId: Int): WidgetModifier.Cold = this.copy(id = newId)
33 | }
34 |
35 | data class EvaluatedHeightModifier(override val id: Int, val height: Dp) :
36 | WidgetModifier.Evaluated {
37 | override fun addToModifier(modifier: Modifier, scope: Any) = modifier.height(height)
38 | }
39 |
--------------------------------------------------------------------------------
/feature/home/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | android { namespace = "io.github.sadellie.sukko.feature.home" }
9 |
10 | dependencies {
11 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
12 | implementation(compose.components.resources)
13 | implementation(compose.components.uiToolingPreview)
14 | implementation(compose.ui)
15 | implementation(libs.androidx.core.ktx)
16 | implementation(libs.androidx.navigation3.navigation3.runtime)
17 | implementation(libs.co.touchlab.kermit)
18 | implementation(libs.com.composables.core)
19 | implementation(libs.com.squareup.okio.okio)
20 | implementation(libs.io.coil.kt.coil3.coil.compose)
21 | implementation(libs.io.github.vinceglb.filekit.dialogs.compose)
22 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
23 | implementation(libs.org.jetbrains.compose.material3.material3)
24 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
25 | implementation(project(":core:common"))
26 | implementation(project(":core:data"))
27 | implementation(project(":core:designsystem"))
28 | implementation(project(":core:fontfiles"))
29 | implementation(project(":core:importexport"))
30 | implementation(project(":core:model"))
31 | implementation(project(":core:ui"))
32 | implementation(project(":core:widget"))
33 | implementation(project(":material-symbols"))
34 | }
35 |
36 | compose.resources.generateResClass = compose.resources.never
37 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | // wasm
5 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
6 | google {
7 | content {
8 | includeGroupByRegex("com\\.android.*")
9 | includeGroupByRegex("com\\.google.*")
10 | includeGroupByRegex("androidx.*")
11 | }
12 | }
13 | mavenCentral()
14 | gradlePluginPortal()
15 | }
16 | }
17 |
18 | dependencyResolutionManagement {
19 | repositories {
20 | // wasm
21 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
22 | google()
23 | mavenCentral()
24 | }
25 | }
26 |
27 | rootProject.name = "sukko"
28 |
29 | with(this) {
30 | // wrap for ktfmt
31 | include(":app")
32 | include(":core:common")
33 | include(":core:model")
34 | include(":core:script")
35 | include(":core:data")
36 | include(":core:unglance")
37 | include(":core:database")
38 | include(":core:medialistener")
39 | include(":core:remote")
40 | include(":core:widget")
41 | include(":core:importexport")
42 | include(":core:routes")
43 | include(":core:designsystem")
44 | include(":core:ui")
45 | include(":core:fontfiles")
46 | include(":core:iconfiles")
47 | include(":feature:editor")
48 | include(":feature:presetselector")
49 | include(":feature:saveaspreset")
50 | include(":feature:fontseditor")
51 | include(":feature:home")
52 | include(":feature:icopackeditor")
53 | include(":feature:importpreset")
54 | include(":feature:settings")
55 | include(":material-symbols")
56 | include(":themmo")
57 | }
58 |
--------------------------------------------------------------------------------
/core/common/src/commonMain/kotlin/io/github/sadellie/sukko/core/common/ColorUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | /** Formats [this] into a hex (like 123456 or 12345678). Will add alpha only if needed. */
6 | fun Color.toHex(): String {
7 | var result = ""
8 | if (alpha != 1f) {
9 | result += this.alpha.asHexString()
10 | }
11 |
12 | result += this.red.asHexString()
13 | result += this.green.asHexString()
14 | result += this.blue.asHexString()
15 | return result.uppercase()
16 | }
17 |
18 | /**
19 | * Convert a hex into a [Color].
20 | *
21 | * Allowed formats:
22 | * - `123456` no hashtag and no alpha
23 | * - `#123456` with hashtag and no alpha
24 | * - `FF123456` no hashtag with alpha
25 | * - `#FF123456` with hashtag and alpha
26 | */
27 | fun String.hexToColor(): Color {
28 | var stringToParse = this.removePrefix("#").uppercase()
29 | if (stringToParse.length == SHORT_HEX_LENGTH) {
30 | // hex without alpha 123456 -> FF123546
31 | stringToParse = "FF$stringToParse"
32 | }
33 | val alpha = stringToParse.take(2).toInt(16)
34 | val red = stringToParse.substring(2, 4).toInt(16)
35 | val green = stringToParse.substring(4, 6).toInt(16)
36 | val blue = stringToParse.substring(6, 8).toInt(16)
37 | return Color(red, green, blue, alpha)
38 | }
39 |
40 | @Suppress("MagicNumber")
41 | private fun Float.asHexString(): String = (this * 255).toInt().toByte().toHexString()
42 |
43 | /**
44 | * - short hex example: ABC123
45 | * - long hex example: FFABC123
46 | */
47 | private const val SHORT_HEX_LENGTH = 6
48 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/DoNotVisitIfBlocksTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FunctionNode
5 | import io.github.sadellie.sukko.core.script.NumberNode
6 | import io.github.sadellie.sukko.core.script.PlusNode
7 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
8 | import io.github.sadellie.sukko.core.script.token.Token3
9 | import kotlin.test.Test
10 |
11 | class DoNotVisitIfBlocksTest {
12 |
13 | @Test
14 | fun doNotVisitIf_test1() {
15 | // visit condition
16 | assertSimplify(
17 | input = "if(1 + 2, 4, 5 + 6)",
18 | expected =
19 | FunctionNode(
20 | Token3.Function.If,
21 | NumberNode(3.0),
22 | NumberNode(4),
23 | PlusNode(NumberNode(5), NumberNode(6)),
24 | ),
25 | )
26 | }
27 |
28 | @Test
29 | fun doNotVisitIf_test2() {
30 | // do not visit ifTrue and ifFalse parameters
31 | assertSimplify(input = "if(1, 2 + 3, 4 + 5)", expected = null)
32 | }
33 |
34 | @Test
35 | fun doNotVisitIf_test3() {
36 | // skip if condition is not simplified yet
37 | assertSimplify(input = "if(1 > 3, 4, 5 + 6)", expected = null)
38 | }
39 |
40 | private fun assertSimplify(input: String, expected: ASTNode?) {
41 | assertSimplifySingleRule(
42 | rule = SimplifyPlusNumbers,
43 | simplificationType = SimplificationType.SUM_OF_NUMBERS,
44 | input = input,
45 | expected = expected,
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/feature/importpreset/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("sukko.android.library")
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.compose.compiler)
5 | alias(libs.plugins.serialization)
6 | }
7 |
8 | android { namespace = "io.github.sadellie.sukko.feature.importpreset" }
9 |
10 | dependencies {
11 | implementation(project.dependencies.platform(libs.io.insert.koin.koin.bom))
12 | implementation(compose.components.resources)
13 | implementation(compose.components.uiToolingPreview)
14 | implementation(compose.ui)
15 | implementation(libs.androidx.core.ktx)
16 | implementation(libs.androidx.navigation3.navigation3.runtime)
17 | implementation(libs.co.touchlab.kermit)
18 | implementation(libs.com.composables.core)
19 | implementation(libs.com.squareup.okio.okio)
20 | implementation(libs.io.coil.kt.coil3.coil.compose)
21 | implementation(libs.io.github.vinceglb.filekit.dialogs.compose)
22 | implementation(libs.io.insert.koin.koin.compose.viewmodel)
23 | implementation(libs.org.jetbrains.compose.material3.material3)
24 | implementation(libs.org.jetbrains.kotlinx.kotlinx.serialization.json)
25 | implementation(project(":core:common"))
26 | implementation(project(":core:data"))
27 | implementation(project(":core:designsystem"))
28 | implementation(project(":core:fontfiles"))
29 | implementation(project(":core:iconfiles"))
30 | implementation(project(":core:importexport"))
31 | implementation(project(":core:model"))
32 | implementation(project(":core:ui"))
33 | implementation(project(":material-symbols"))
34 | }
35 |
36 | compose.resources.generateResClass = compose.resources.never
37 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyLessOrEqualCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyLessOrEqualCheckTest {
11 | @Test fun simplify_lessOrEqual1() = assertSimplify("2 <= 2", TrueNode)
12 |
13 | @Test fun simplify_lessOrEqual2() = assertSimplify("1 <= 2", TrueNode)
14 |
15 | @Test fun simplify_lessOrEqual3() = assertSimplify("\"2\" <= 2", null)
16 |
17 | @Test fun simplify_lessOrEqual4() = assertSimplify("(2 * 3) <= 6", null)
18 |
19 | @Test fun simplify_lessOrEqual5() = assertSimplify("(2 * 3) <= (2 * 3)", null)
20 |
21 | @Test fun simplify_lessOrEqual6() = assertSimplify("true <= true", TrueNode)
22 |
23 | @Test fun simplify_lessOrEqual7() = assertSimplify("false <= false", TrueNode)
24 |
25 | @Test fun simplify_lessOrEqual8() = assertSimplify("true <= false", FalseNode)
26 |
27 | @Test fun simplify_lessOrEqual9() = assertSimplify("false <= true", TrueNode)
28 |
29 | private fun assertSimplify(input: String, expected: ASTNode?) {
30 | assertSimplifySingleRule(
31 | rule = SimplifyLessOrEqualCheck,
32 | simplificationType = SimplificationType.LESS_OR_EQUAL,
33 | input = input,
34 | expected = expected,
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierSize.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdSizeModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DpSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierSize(
17 | modifier: Modifier,
18 | widgetModifier: ColdSizeModifier,
19 | onUpdateModifier: (ColdSizeModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.size),
27 | state = state,
28 | )
29 |
30 | DpSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(size = it)) },
33 | value = widgetModifier.size,
34 | globals = state.globals.dps,
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/material-symbols/src/commonMain/kotlin/google/material/design/symbols/LineEnd.kt:
--------------------------------------------------------------------------------
1 | package google.material.design.symbols
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Symbols.LineEnd: ImageVector
10 | get() {
11 | if (_LineEnd != null) {
12 | return _LineEnd!!
13 | }
14 | _LineEnd = ImageVector.Builder(
15 | name = "LineEnd",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 960f,
19 | viewportHeight = 960f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFFE3E3E3))) {
22 | moveTo(780f, 580f)
23 | quadToRelative(-31f, 0f, -56f, -17f)
24 | reflectiveQuadToRelative(-36f, -43f)
25 | lineTo(80f, 520f)
26 | verticalLineToRelative(-80f)
27 | horizontalLineToRelative(608f)
28 | quadToRelative(11f, -26f, 36f, -43f)
29 | reflectiveQuadToRelative(56f, -17f)
30 | quadToRelative(42f, 0f, 71f, 29f)
31 | reflectiveQuadToRelative(29f, 71f)
32 | quadToRelative(0f, 42f, -29f, 71f)
33 | reflectiveQuadToRelative(-71f, 29f)
34 | close()
35 | }
36 | }.build()
37 |
38 | return _LineEnd!!
39 | }
40 |
41 | @Suppress("ObjectPropertyName")
42 | private var _LineEnd: ImageVector? = null
43 |
--------------------------------------------------------------------------------
/core/data/src/androidInstrumentedTest/kotlin/io/github/sadellie/sukko/core/data/WidgetDataRepositoryDeleteTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import androidx.room.Room
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import io.github.sadellie.sukko.core.common.filesPath
6 | import io.github.sadellie.sukko.core.database.SukkoDatabase
7 | import io.github.sadellie.sukko.core.model.WidgetData
8 | import kotlinx.coroutines.runBlocking
9 | import org.junit.Test
10 |
11 | class WidgetDataRepositoryDeleteTest {
12 | private val context =
13 | InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
14 | private val db = Room.inMemoryDatabaseBuilder(context).build()
15 | private val widgetDataDao = db.widgetDataDao()
16 | private val widgetDataRepository =
17 | WidgetDataRepositoryImpl(dao = widgetDataDao, context = context, removeImageFromCache = {})
18 |
19 | @Test
20 | fun delete_nonExisting() = runBlocking {
21 | // should not throw anything
22 | widgetDataRepository.delete(18)
23 | assert(true)
24 | }
25 |
26 | @Test
27 | fun delete_withAllFiles() = runBlocking {
28 | val widgetData = WidgetData(appWidgetId = 20, name = "Widget 1", layers = emptyList())
29 | widgetDataRepository.save(
30 | widgetData = widgetData,
31 | evaluatedLayers = emptyList(),
32 | previewImageBitmap = null,
33 | )
34 | widgetDataRepository.delete(20)
35 |
36 | val widgetDataDir = widgetData.getPreviewPath(context.filesPath).parent!!.toFile()
37 | val isExists = widgetDataDir.exists()
38 |
39 | assert(!isExists)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/FillMaxHeightModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.fillMaxHeight
4 | import androidx.compose.ui.Modifier
5 | import io.github.sadellie.sukko.core.model.Globals
6 | import io.github.sadellie.sukko.core.model.LayerContext
7 | import io.github.sadellie.sukko.core.model.basic.ScriptableDouble
8 | import io.github.sadellie.sukko.resources.Res
9 | import io.github.sadellie.sukko.resources.core_model_modifier_fill_max_height
10 | import kotlinx.serialization.Serializable
11 | import kotlinx.serialization.Transient
12 |
13 | @Serializable
14 | data class ColdFillMaxHeightModifier(
15 | override val id: Int,
16 | val fraction: ScriptableDouble = ScriptableDouble.Fixed(1.0),
17 | ) : WidgetModifier.Cold {
18 | @Transient override val displayName = Res.string.core_model_modifier_fill_max_height
19 |
20 | companion object {
21 | val fractionRange by lazy { 0.0..1.0 }
22 | }
23 |
24 | override suspend fun evaluate(
25 | layerContext: LayerContext,
26 | globals: Globals,
27 | ): WidgetModifier.Evaluated =
28 | EvaluatedFillMaxHeightModifier(
29 | id = id,
30 | fraction = fraction.getValue(layerContext, globals).coerceIn(fractionRange).toFloat(),
31 | )
32 |
33 | override fun updateId(newId: Int) = this.copy(id = newId)
34 | }
35 |
36 | data class EvaluatedFillMaxHeightModifier(override val id: Int, val fraction: Float) :
37 | WidgetModifier.Evaluated {
38 | override fun addToModifier(modifier: Modifier, scope: Any) =
39 | modifier.fillMaxHeight(fraction = fraction)
40 | }
41 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/ErrorScreenPlaceholder.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import google.material.design.symbols.Error
10 | import google.material.design.symbols.Symbols
11 | import io.github.sadellie.sukko.resources.Res
12 | import io.github.sadellie.sukko.resources.common_error
13 | import io.github.sadellie.sukko.resources.common_error_text
14 | import org.jetbrains.compose.resources.stringResource
15 | import org.jetbrains.compose.ui.tooling.preview.Preview
16 |
17 | @OptIn(ExperimentalMaterial3Api::class)
18 | @Composable
19 | fun ErrorScreenPlaceholder(
20 | onNavigateUp: () -> Unit,
21 | screenTitle: String = stringResource(Res.string.common_error),
22 | bodyTitle: String = stringResource(Res.string.common_error),
23 | text: String = stringResource(Res.string.common_error_text),
24 | ) {
25 | ScaffoldWithTopAppBar(
26 | title = { Text(screenTitle) },
27 | navigationIcon = { NavigateUpButton(onNavigateUp) },
28 | ) { paddingValues ->
29 | ScenePlaceholder(
30 | modifier = Modifier.padding(paddingValues).fillMaxSize(),
31 | icon = Symbols.Error,
32 | title = bodyTitle,
33 | text = text,
34 | )
35 | }
36 | }
37 |
38 | @Composable
39 | @Preview
40 | private fun PreviewErrorScreenPlaceholder() {
41 | ErrorScreenPlaceholder(onNavigateUp = {})
42 | }
43 |
--------------------------------------------------------------------------------
/core/iconfiles/src/commonMain/kotlin/io/github/sadellie/sukko/core/iconfiles/IconFile.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.iconfiles
2 |
3 | import io.github.sadellie.sukko.core.common.ASSET_PATH
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.Transient
6 | import okio.Path
7 |
8 | @Serializable
9 | sealed interface IconPack {
10 | val iconPackId: Long
11 | val name: String
12 |
13 | fun getFullPath(filesDirPath: Path): Path
14 |
15 | companion object {
16 | private const val DIR_PATH = "iconPacks"
17 |
18 | fun builtIns(): List = listOf(MaterialSymbolsRounded)
19 | }
20 |
21 | @Serializable
22 | sealed interface BuiltIn : IconPack {
23 | override fun getFullPath(filesDirPath: Path) = ASSET_PATH / DIR_PATH / iconPackId.toString()
24 | }
25 |
26 | @Serializable
27 | data object MaterialSymbolsRounded : BuiltIn {
28 | override val name = "Material Symbols Rounded"
29 | override val iconPackId = -1L
30 | }
31 |
32 | @Serializable
33 | data class Custom(override val iconPackId: Long, override val name: String) : IconPack {
34 | override fun getFullPath(filesDirPath: Path): Path =
35 | filesDirPath / DIR_PATH / iconPackId.toString()
36 | }
37 | }
38 |
39 | /** @property fileName Name of the icon file, not path. For example: file_1.svg */
40 | @Serializable
41 | data class IconFile(val fileName: String, val iconPack: IconPack) {
42 | fun getFullPath(filesDirPath: Path): Path = iconPack.getFullPath(filesDirPath) / fileName
43 |
44 | @Transient val name = fileName.removeSuffix(".$EXTENSION")
45 |
46 | companion object {
47 | const val EXTENSION = "svg"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/simplify/equality/SimplifyGreaterOrEqualCheckTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify.equality
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.FalseNode
5 | import io.github.sadellie.sukko.core.script.TrueNode
6 | import io.github.sadellie.sukko.core.script.assertSimplifySingleRule
7 | import io.github.sadellie.sukko.core.script.simplify.SimplificationType
8 | import kotlin.test.Test
9 |
10 | class SimplifyGreaterOrEqualCheckTest {
11 | @Test fun simplify_greaterOrEqual1() = assertSimplify("2 >= 2", TrueNode)
12 |
13 | @Test fun simplify_greaterOrEqual2() = assertSimplify("1 >= 2", FalseNode)
14 |
15 | @Test fun simplify_greaterOrEqual3() = assertSimplify("\"2\" >= 2", null)
16 |
17 | @Test fun simplify_greaterOrEqual4() = assertSimplify("(2 * 3) >= 6", null)
18 |
19 | @Test fun simplify_greaterOrEqual5() = assertSimplify("(2 * 3) >= (2 * 3)", null)
20 |
21 | @Test fun simplify_greaterOrEqual6() = assertSimplify("true >= true", TrueNode)
22 |
23 | @Test fun simplify_greaterOrEqual7() = assertSimplify("false >= false", TrueNode)
24 |
25 | @Test fun simplify_greaterOrEqual8() = assertSimplify("true >= false", TrueNode)
26 |
27 | @Test fun simplify_greaterOrEqual9() = assertSimplify("false >= true", FalseNode)
28 |
29 | private fun assertSimplify(input: String, expected: ASTNode?) {
30 | assertSimplifySingleRule(
31 | rule = SimplifyGreaterOrEqualCheck,
32 | simplificationType = SimplificationType.GREATER_OR_EQUAL,
33 | input = input,
34 | expected = expected,
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierWidth.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdWidthModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DpSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierWidth(
17 | modifier: Modifier,
18 | widgetModifier: ColdWidthModifier,
19 | onUpdateModifier: (ColdWidthModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.width),
27 | state = state,
28 | )
29 |
30 | DpSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(width = it)) },
33 | value = widgetModifier.width,
34 | range = ColdWidthModifier.widthRange,
35 | globals = state.globals.dps,
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierBoxAlignment.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
9 | import io.github.sadellie.sukko.core.model.modifier.ColdBoxAlignmentModifier
10 | import io.github.sadellie.sukko.core.ui.ModalBottomSheetWithItems
11 | import io.github.sadellie.sukko.core.ui.expand
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierBoxAlignment(
17 | modifier: Modifier,
18 | widgetModifier: ColdBoxAlignmentModifier,
19 | onUpdateModifier: (ColdBoxAlignmentModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = stringResource(widgetModifier.alignmentSource.displayName),
27 | state = state,
28 | )
29 |
30 | ModalBottomSheetWithItems(
31 | state = sheetState,
32 | items = AlignmentSource.allBoth,
33 | headlineText = { stringResource(it.displayName) },
34 | onClick = { onUpdateModifier(widgetModifier.copy(alignmentSource = it)) },
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/core/common/src/commonMain/kotlin/io/github/sadellie/sukko/core/common/Serializer.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.common
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.unit.Dp
5 | import androidx.compose.ui.unit.TextUnit
6 | import androidx.compose.ui.unit.sp
7 | import kotlinx.serialization.KSerializer
8 | import kotlinx.serialization.descriptors.PrimitiveKind
9 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
10 | import kotlinx.serialization.descriptors.SerialDescriptor
11 | import kotlinx.serialization.encoding.Decoder
12 | import kotlinx.serialization.encoding.Encoder
13 |
14 | class ColorSerializer : KSerializer {
15 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.LONG)
16 |
17 | override fun serialize(encoder: Encoder, value: Color) = encoder.encodeLong(value.value.toLong())
18 |
19 | override fun deserialize(decoder: Decoder): Color = Color(decoder.decodeLong().toULong())
20 | }
21 |
22 | class DpSerializer : KSerializer {
23 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Dp", PrimitiveKind.FLOAT)
24 |
25 | override fun serialize(encoder: Encoder, value: Dp) = encoder.encodeFloat(value.value)
26 |
27 | override fun deserialize(decoder: Decoder): Dp = Dp(decoder.decodeFloat())
28 | }
29 |
30 | class SpSerializer : KSerializer {
31 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Sp", PrimitiveKind.FLOAT)
32 |
33 | override fun serialize(encoder: Encoder, value: TextUnit) = encoder.encodeFloat(value.value)
34 |
35 | override fun deserialize(decoder: Decoder): TextUnit = decoder.decodeFloat().sp
36 | }
37 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierHeight.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdHeightModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DpSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierHeight(
17 | modifier: Modifier,
18 | widgetModifier: ColdHeightModifier,
19 | onUpdateModifier: (ColdHeightModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.height),
27 | state = state,
28 | )
29 |
30 | DpSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(height = it)) },
33 | value = widgetModifier.height,
34 | range = ColdHeightModifier.heightRange,
35 | globals = state.globals.dps,
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierRowAlignment.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
9 | import io.github.sadellie.sukko.core.model.modifier.ColdRowAlignmentModifier
10 | import io.github.sadellie.sukko.core.ui.ModalBottomSheetWithItems
11 | import io.github.sadellie.sukko.core.ui.expand
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierRowAlignment(
17 | modifier: Modifier,
18 | widgetModifier: ColdRowAlignmentModifier,
19 | onUpdateModifier: (ColdRowAlignmentModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = stringResource(widgetModifier.alignmentSource.displayName),
27 | state = state,
28 | )
29 |
30 | ModalBottomSheetWithItems(
31 | state = sheetState,
32 | items = AlignmentSource.allVertical,
33 | headlineText = { stringResource(it.displayName) },
34 | onClick = { onUpdateModifier(widgetModifier.copy(alignmentSource = it)) },
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyMultiplyNumbers.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.MultiplyNode
5 | import io.github.sadellie.sukko.core.script.NumberNode
6 | import io.github.sadellie.sukko.core.script.ScriptContext
7 |
8 | /**
9 | * - 1 * 2 = 2
10 | * - 1 * 2 * 3 = 6
11 | * - 1 * (2 + 3) = null
12 | */
13 | internal val SimplifyMultiplyNumbers =
14 | object : SimplificationRule {
15 | override suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep? =
16 | simplifyBottomToTop(SimplificationType.PRODUCT_OF_NUMBERS, context, tree) { currentNode ->
17 | // only work with multiply node. skip to child and move to neighbour
18 | if (currentNode !is MultiplyNode) return@simplifyBottomToTop null
19 |
20 | // find numbers in this multiply node
21 | val numberNodes = currentNode.children.filterIsInstance()
22 | if (numberNodes.size < 2) return@simplifyBottomToTop null
23 | // multiply up all found number nodes
24 | val productOfNumberNodes = numberNodes.fold(1.0) { left, right -> left * right.toDouble() }
25 | val productAsNumberNode = NumberNode(productOfNumberNodes)
26 |
27 | val updatedChildren =
28 | currentNode.children
29 | // remove number nodes
30 | .filter { child -> child !is NumberNode }
31 | // place their product
32 | .plus(productAsNumberNode)
33 |
34 | return@simplifyBottomToTop currentNode.withNewChildren(updatedChildren)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/ui/src/commonMain/kotlin/io/github/sadellie/sukko/core/ui/ListHeader.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.ui
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.material3.ListItemDefaults
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.text.TextStyle
13 | import io.github.sadellie.sukko.core.designsystem.theme.ListArrangement
14 | import io.github.sadellie.sukko.core.designsystem.theme.Sizes
15 | import org.jetbrains.compose.ui.tooling.preview.Preview
16 |
17 | @Composable
18 | fun ListHeader(
19 | text: String,
20 | modifier: Modifier =
21 | Modifier.padding(
22 | start = Sizes.small,
23 | end = Sizes.small,
24 | top = Sizes.large,
25 | bottom = Sizes.small,
26 | ),
27 | color: Color = MaterialTheme.colorScheme.onSurface,
28 | style: TextStyle = MaterialTheme.typography.labelLarge,
29 | ) {
30 | Text(text = text, modifier = modifier, color = color, style = style)
31 | }
32 |
33 | @Composable
34 | @Preview
35 | private fun PreviewListHeader() {
36 | LazyColumn(
37 | modifier = Modifier.background(MaterialTheme.colorScheme.background),
38 | verticalArrangement = ListArrangement,
39 | ) {
40 | item { ListHeader("Text") }
41 | items(10) {
42 | ListItem2(
43 | headlineContent = { Text("Item $it") },
44 | shape = ListItemDefaults.listedShape(it, 10),
45 | )
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/material-symbols/src/commonMain/kotlin/google/material/design/symbols/Check.kt:
--------------------------------------------------------------------------------
1 | package google.material.design.symbols
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Symbols.Check: ImageVector
10 | get() {
11 | if (_Check != null) {
12 | return _Check!!
13 | }
14 | _Check =
15 | ImageVector.Builder(
16 | name = "Check",
17 | defaultWidth = 24.dp,
18 | defaultHeight = 24.dp,
19 | viewportWidth = 960f,
20 | viewportHeight = 960f,
21 | )
22 | .apply {
23 | path(fill = SolidColor(Color.Black)) {
24 | moveToRelative(382f, 606f)
25 | lineToRelative(339f, -339f)
26 | quadToRelative(12f, -12f, 28f, -12f)
27 | reflectiveQuadToRelative(28f, 12f)
28 | quadToRelative(12f, 12f, 12f, 28.5f)
29 | reflectiveQuadTo(777f, 324f)
30 | lineTo(410f, 692f)
31 | quadToRelative(-12f, 12f, -28f, 12f)
32 | reflectiveQuadToRelative(-28f, -12f)
33 | lineTo(182f, 520f)
34 | quadToRelative(-12f, -12f, -11.5f, -28.5f)
35 | reflectiveQuadTo(183f, 463f)
36 | quadToRelative(12f, -12f, 28.5f, -12f)
37 | reflectiveQuadToRelative(28.5f, 12f)
38 | lineToRelative(142f, 143f)
39 | close()
40 | }
41 | }
42 | .build()
43 |
44 | return _Check!!
45 | }
46 |
47 | @Suppress("ObjectPropertyName") private var _Check: ImageVector? = null
48 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierColumnAlignment.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
9 | import io.github.sadellie.sukko.core.model.modifier.ColdColumnAlignmentModifier
10 | import io.github.sadellie.sukko.core.ui.ModalBottomSheetWithItems
11 | import io.github.sadellie.sukko.core.ui.expand
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierColumnAlignment(
17 | modifier: Modifier,
18 | widgetModifier: ColdColumnAlignmentModifier,
19 | onUpdateModifier: (ColdColumnAlignmentModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = stringResource(widgetModifier.alignmentSource.displayName),
27 | state = state,
28 | )
29 |
30 | ModalBottomSheetWithItems(
31 | state = sheetState,
32 | items = AlignmentSource.allHorizontal,
33 | headlineText = { stringResource(it.displayName) },
34 | onClick = { onUpdateModifier(widgetModifier.copy(alignmentSource = it)) },
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/core/script/src/commonTest/kotlin/io/github/sadellie/sukko/core/script/ToFormattedStringTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 |
6 | class ToFormattedStringTest {
7 | @Test
8 | fun toFormattedString_atomicNode() {
9 | assertFormat("\"text\"", "\"text\"")
10 | assertFormat("123", "123")
11 | assertFormat("123.0", "123.0")
12 | assertFormat("123.456", "123.456")
13 | }
14 |
15 | @Test
16 | fun toFormattedString_operatorNode() {
17 | assertFormat("123 + 456", "123 + 456")
18 | assertFormat("123 + 456 + 789", "123 + 456 + 789")
19 | // minus doesn't exist
20 | assertFormat("123 * 456 - 789", "123 * 456 + -789")
21 | assertFormat("123 * 456 <= 789", "123 * 456 <= 789")
22 | }
23 |
24 | @Test
25 | fun toFormattedString_functionNode() {
26 | assertFormat("""currentDate("HH")""", """currentDate("HH")""")
27 | assertFormat(
28 | """if((1 + 2) * 4, currentDate("HH"), currentDate("mm"))""",
29 | """if((1 + 2) * 4, currentDate("HH"), currentDate("mm"))""",
30 | )
31 | }
32 |
33 | @Test
34 | fun toFormattedString_unaryOperatorNode() {
35 | assertFormat("-456", "-456")
36 | assertFormat("-(123+456)", "-(123 + 456)")
37 | }
38 |
39 | @Test
40 | fun toFormattedString_bracketsNode() {
41 | assertFormat("(123)", "123")
42 | assertFormat("((123+456) + 789)", "((123 + 456) + 789)")
43 | }
44 |
45 | // most of the time input is same after processing
46 | private fun assertFormat(input: String, expected: String) {
47 | val tree = buildTreeAndCollapse(input)
48 | val actual = tree.toFormattedString()
49 | assertEquals(expected, actual)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierAlpha.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdAlphaModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DoubleSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierAlpha(
17 | modifier: Modifier,
18 | widgetModifier: ColdAlphaModifier,
19 | onUpdateModifier: (ColdAlphaModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.alpha),
27 | state = state,
28 | )
29 |
30 | DoubleSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(alpha = it)) },
33 | value = widgetModifier.alpha,
34 | range = ColdAlphaModifier.alphaRange,
35 | allowFraction = true,
36 | globals = state.globals.doubles,
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierPaddingAllSides.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdPaddingAllSidesModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DpSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierPaddingAllSides(
17 | modifier: Modifier,
18 | widgetModifier: ColdPaddingAllSidesModifier,
19 | onUpdateModifier: (ColdPaddingAllSidesModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.all),
27 | state = state,
28 | )
29 |
30 | DpSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(all = it)) },
33 | value = widgetModifier.all,
34 | range = ColdPaddingAllSidesModifier.valueRange,
35 | globals = state.globals.dps,
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/feature/editor/src/androidMain/kotlin/io/github/sadellie/sukko/feature/editor/selector/scripteditor/InputPage.android.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.selector.scripteditor
2 |
3 | import android.content.Intent
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.IconButton
7 | import androidx.compose.material3.IconButtonDefaults
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.platform.LocalContext
11 | import google.material.design.symbols.AddPhotoAlternate
12 | import google.material.design.symbols.Symbols
13 | import io.github.sadellie.sukko.core.common.uri
14 | import io.github.vinceglb.filekit.dialogs.compose.rememberFilePickerLauncher
15 |
16 | @Composable
17 | internal actual fun AddImageLinkButton(onAdd: (String) -> Unit) {
18 | val context = LocalContext.current
19 | val launcher = rememberFilePickerLauncher { file ->
20 | if (file == null) return@rememberFilePickerLauncher
21 | val pickedFileUri = file.uri()
22 | context.contentResolver.takePersistableUriPermission(
23 | pickedFileUri,
24 | Intent.FLAG_GRANT_READ_URI_PERMISSION,
25 | )
26 | onAdd(pickedFileUri.toString())
27 | }
28 | IconButton(
29 | modifier =
30 | Modifier.size(
31 | IconButtonDefaults.smallContainerSize(IconButtonDefaults.IconButtonWidthOption.Uniform)
32 | ),
33 | onClick = launcher::launch,
34 | shapes = IconButtonDefaults.shapes(),
35 | ) {
36 | Icon(
37 | imageVector = Symbols.AddPhotoAlternate,
38 | contentDescription = null,
39 | modifier = Modifier.size(IconButtonDefaults.smallIconSize),
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/simplify/SimplifyPlusNumbers.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.simplify
2 |
3 | import io.github.sadellie.sukko.core.script.ASTNode
4 | import io.github.sadellie.sukko.core.script.NumberNode
5 | import io.github.sadellie.sukko.core.script.PlusNode
6 | import io.github.sadellie.sukko.core.script.ScriptContext
7 |
8 | /**
9 | * - 1 + 2 = 3
10 | * - 1 + 2 + 3 = 6
11 | * - 1 + (2 + 3) = 1 + 5
12 | * - 1 - 2 - 3 - 4 = 1 - 9
13 | * - -1 - 2 - 3 = -6
14 | */
15 | internal val SimplifyPlusNumbers =
16 | object : SimplificationRule {
17 | override suspend fun simplify(context: ScriptContext, tree: ASTNode): SimplificationStep? =
18 | simplifyBottomToTop(SimplificationType.SUM_OF_NUMBERS, context, tree) { currentNode ->
19 | // only work with plus node. skip to child and move to neighbour
20 | if (currentNode !is PlusNode) return@simplifyBottomToTop null
21 |
22 | // find numbers in this plus node
23 | val numberNodes = currentNode.children.filterIsInstance()
24 | if (numberNodes.size < 2) return@simplifyBottomToTop null
25 | // sum up all found number nodes
26 | val sumOfNumberNodes = numberNodes.sumOf { numberNode -> numberNode.toDouble() }
27 | val sumAsNumberNode = NumberNode(sumOfNumberNodes)
28 |
29 | val updatedChildren =
30 | currentNode.children
31 | // remove number nodes
32 | .filter { child -> child !is NumberNode }
33 | // place their sum
34 | .plus(sumAsNumberNode)
35 |
36 | val updatedNode = currentNode.withNewChildren(updatedChildren)
37 | return@simplifyBottomToTop updatedNode
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/wasmJsMain/kotlin/io/github/sadellie/sukko/main.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material3.Button
5 | import androidx.compose.material3.ButtonDefaults
6 | import androidx.compose.material3.Text
7 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
8 | import androidx.compose.material3.windowsizeclass.WindowSizeClass
9 | import androidx.compose.runtime.CompositionLocalProvider
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.ExperimentalComposeUiApi
12 | import androidx.compose.ui.platform.LocalDensity
13 | import androidx.compose.ui.platform.LocalWindowInfo
14 | import androidx.compose.ui.unit.DpSize
15 | import androidx.compose.ui.window.ComposeViewport
16 | import io.github.sadellie.sukko.core.designsystem.LocalWindowSize
17 | import kotlinx.browser.document
18 |
19 | @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3WindowSizeClassApi::class)
20 | fun main() {
21 | ComposeViewport(document.body!!) {
22 | val containerSize = LocalWindowInfo.current.containerSize
23 | val density = LocalDensity.current
24 | val windowSizeClass =
25 | remember(containerSize, density) {
26 | with(density) {
27 | WindowSizeClass.calculateFromSize(
28 | DpSize(containerSize.width.toDp(), containerSize.height.toDp())
29 | )
30 | }
31 | }
32 | CompositionLocalProvider(LocalWindowSize provides windowSizeClass) {
33 | Column {
34 | Text("Main app: $windowSizeClass")
35 | Button(onClick = { callLogs() }, shapes = ButtonDefaults.shapes()) { Text("Call logs") }
36 | }
37 | }
38 | }
39 | }
40 |
41 | external fun callLogs()
42 |
--------------------------------------------------------------------------------
/core/data/src/commonMain/kotlin/io/github/sadellie/sukko/core/data/LayerContextImpl.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.data
2 |
3 | import androidx.compose.ui.text.font.FontFamily
4 | import io.github.sadellie.sukko.core.common.SuspendLazy
5 | import io.github.sadellie.sukko.core.fontfiles.FontFile
6 | import io.github.sadellie.sukko.core.model.LayerContext
7 | import io.github.sadellie.sukko.core.model.data.BatteryInfoProvider
8 | import io.github.sadellie.sukko.core.model.data.DateTimeProvider
9 | import io.github.sadellie.sukko.core.model.data.DynamicColorSchemeProvider
10 | import io.github.sadellie.sukko.core.model.data.MediaInfoProvider
11 | import kotlin.coroutines.CoroutineContext
12 | import okio.Path
13 |
14 | /**
15 | * @property parentCoroutineContext Coroutine context in which [SuspendLazy] will be invoked. Layer
16 | * will create a children coroutine context and invoke all [SuspendLazy] values in it.
17 | */
18 | expect class LayerContextImpl : LayerContext {
19 | val parentCoroutineContext: CoroutineContext
20 | override val filesDirPath: Path
21 | override val batteryInfoProvider: BatteryInfoProvider
22 | override val mediaInfoProvider: MediaInfoProvider
23 | override val dynamicColorSchemeProvider: DynamicColorSchemeProvider
24 | override val dateTimeProvider: DateTimeProvider
25 | override val deviceModel: String
26 |
27 | override suspend fun loadAndCacheImage(uri: String): String
28 |
29 | override suspend fun loadFontFamily(fontFile: FontFile): FontFamily
30 |
31 | override fun invalidateOnAlarmProviders(): LayerContext
32 |
33 | override fun invalidateMediaInfoProvider(): LayerContext
34 | }
35 |
36 | expect class LayerContextProvider() {
37 | fun provide(parentCoroutineContext: CoroutineContext): LayerContext
38 | }
39 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/BoxAlignmentModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.BoxScope
4 | import androidx.compose.ui.Alignment
5 | import androidx.compose.ui.Modifier
6 | import co.touchlab.kermit.Logger
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_box_alignment
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdBoxAlignmentModifier(
17 | override val id: Int,
18 | val alignmentSource: AlignmentSource.Both = AlignmentSource.TopStart,
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_box_alignment
21 |
22 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
23 | EvaluatedBoxAlignmentModifier(id = id, alignment = alignmentSource.getAlignment())
24 |
25 | override fun updateId(newId: Int) = this.copy(id = newId)
26 | }
27 |
28 | data class EvaluatedBoxAlignmentModifier(override val id: Int, val alignment: Alignment) :
29 | WidgetModifier.Evaluated {
30 | companion object {
31 | private const val TAG = "EvaluatedBoxAlignmentModifier"
32 | }
33 |
34 | override fun addToModifier(modifier: Modifier, scope: Any): Modifier {
35 | if (scope !is BoxScope) {
36 | Logger.w(TAG) { "Wrong scope: $scope" }
37 | return modifier
38 | }
39 | return with(scope) { modifier.align(alignment) }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/RowAlignmentModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.RowScope
4 | import androidx.compose.ui.Alignment
5 | import androidx.compose.ui.Modifier
6 | import co.touchlab.kermit.Logger
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_row_alignment
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdRowAlignmentModifier(
17 | override val id: Int,
18 | val alignmentSource: AlignmentSource.Vertical = AlignmentSource.Top,
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_row_alignment
21 |
22 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
23 | EvaluatedRowAlignmentModifier(id = id, alignment = alignmentSource.getAlignment())
24 |
25 | override fun updateId(newId: Int) = this.copy(id = newId)
26 | }
27 |
28 | data class EvaluatedRowAlignmentModifier(override val id: Int, val alignment: Alignment.Vertical) :
29 | WidgetModifier.Evaluated {
30 | companion object {
31 | private const val TAG = "EvaluatedRowAlignmentModifier"
32 | }
33 |
34 | override fun addToModifier(modifier: Modifier, scope: Any): Modifier {
35 | if (scope !is RowScope) {
36 | Logger.w(TAG) { "Wrong scope: $scope" }
37 | return modifier
38 | }
39 | return with(scope) { modifier.align(alignment) }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierFillMaxWidth.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdFillMaxWidthModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DoubleSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierFillMaxWidth(
17 | modifier: Modifier,
18 | widgetModifier: ColdFillMaxWidthModifier,
19 | onUpdateModifier: (ColdFillMaxWidthModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.fraction),
27 | state = state,
28 | )
29 |
30 | DoubleSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(fraction = it)) },
33 | value = widgetModifier.fraction,
34 | range = ColdFillMaxWidthModifier.fractionRange,
35 | allowFraction = true,
36 | globals = state.globals.doubles,
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierFillMaxHeight.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdFillMaxHeightModifier
10 | import io.github.sadellie.sukko.core.ui.expand
11 | import io.github.sadellie.sukko.feature.editor.selector.DoubleSelectorSheet
12 | import org.jetbrains.compose.resources.stringResource
13 | import sh.calvin.reorderable.ReorderableCollectionItemScope
14 |
15 | @Composable
16 | internal fun ReorderableCollectionItemScope.EditorModifierFillMaxHeight(
17 | modifier: Modifier,
18 | widgetModifier: ColdFillMaxHeightModifier,
19 | onUpdateModifier: (ColdFillMaxHeightModifier) -> Unit,
20 | state: EditorModifierListItemState,
21 | ) {
22 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
23 | EditorModifierFlatListItem(
24 | modifier = modifier.clickable { sheetState.expand() },
25 | headlineText = stringResource(widgetModifier.displayName),
26 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.fraction),
27 | state = state,
28 | )
29 |
30 | DoubleSelectorSheet(
31 | state = sheetState,
32 | onValueSelected = { onUpdateModifier(widgetModifier.copy(fraction = it)) },
33 | value = widgetModifier.fraction,
34 | range = ColdFillMaxHeightModifier.fractionRange,
35 | allowFraction = true,
36 | globals = state.globals.doubles,
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/core/medialistener/src/androidMain/kotlin/io/github/sadellie/sukko/core/medialistener/MediaListenerService.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.medialistener
2 |
3 | import android.app.Service
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.IBinder
7 | import co.touchlab.kermit.Logger
8 | import org.koin.core.component.KoinComponent
9 | import org.koin.core.component.inject
10 |
11 | class MediaListenerService : Service(), KoinComponent {
12 | override fun onBind(intent: Intent?): IBinder? = null
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | Logger.d(TAG) { "onCreate service" }
17 | try {
18 | val mediaListener by inject()
19 | mediaListener.startListening()
20 | } catch (e: Exception) {
21 | Logger.e(TAG, e) { "Failed to start media listener" }
22 | }
23 | }
24 |
25 | override fun onDestroy() {
26 | super.onDestroy()
27 | val mediaListener by inject()
28 | mediaListener.destroy()
29 | }
30 |
31 | companion object {
32 | fun start(context: Context) {
33 | if (!NotificationListener.canAccessNotifications(context)) {
34 | Logger.e(TAG) { "Can not start service: notification listener is not allowed" }
35 | return
36 | }
37 | val intent = getServiceIntent(context)
38 | context.startService(intent)
39 | Logger.d(TAG) { "Media listener started" }
40 | }
41 |
42 | fun stop(context: Context) {
43 | val intent = getServiceIntent(context)
44 | context.stopService(intent)
45 | }
46 |
47 | private fun getServiceIntent(context: Context) =
48 | Intent(context.applicationContext, MediaListenerService::class.java)
49 |
50 | private const val TAG = "MediaListenerService"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/core/script/src/commonMain/kotlin/io/github/sadellie/sukko/core/script/docs/Docs.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.script.docs
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | @SerialName("docs")
8 | data class Docs(
9 | val constants: List,
10 | val methods: List,
11 | val templates: List,
12 | )
13 |
14 | @Serializable
15 | sealed interface DocsItem {
16 | val name: TranslatableString
17 | val returnTypes: List
18 |
19 | @Serializable
20 | @SerialName("constant")
21 | data class Constant(
22 | override val name: TranslatableString,
23 | override val returnTypes: List,
24 | val description: TranslatableString,
25 | val api: String,
26 | ) : DocsItem
27 |
28 | @Serializable
29 | @SerialName("method")
30 | data class Method(
31 | override val name: TranslatableString,
32 | override val returnTypes: List,
33 | val description: TranslatableString,
34 | val api: String,
35 | val params: List,
36 | ) : DocsItem
37 |
38 | @Serializable
39 | @SerialName("template")
40 | data class Template(
41 | override val name: TranslatableString,
42 | override val returnTypes: List,
43 | val script: String,
44 | ) : DocsItem
45 | }
46 |
47 | @Serializable
48 | @SerialName("methodParam")
49 | data class MethodParam(
50 | val name: String,
51 | val description: TranslatableString,
52 | val types: List,
53 | )
54 |
55 | /** Map of language and text in this language */
56 | typealias TranslatableString = Map
57 |
58 | @SerialName("type")
59 | enum class ScriptTypeTag {
60 | TEXT,
61 | NUMBER,
62 | BOOL,
63 | }
64 |
--------------------------------------------------------------------------------
/core/model/src/commonTest/kotlin/io/github/sadellie/sukko/core/model/WidgetDataUpdateLayerTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model
2 |
3 | import io.github.sadellie.sukko.core.model.layer.ColdTextLayer
4 | import kotlin.test.Test
5 | import kotlin.test.assertEquals
6 |
7 | class WidgetDataUpdateLayerTest {
8 | @Test
9 | fun updateLayer_empty() {
10 | val widgetData = WidgetData(appWidgetId = 1)
11 | val expected = WidgetData(appWidgetId = 1)
12 | val actual = widgetData.updateLayer(ColdTextLayer(id = 100))
13 | assertEquals(expected, actual)
14 | }
15 |
16 | @Test
17 | fun updateLayer_invalidLayer() {
18 | val widgetData =
19 | WidgetData(
20 | appWidgetId = 1,
21 | layers = listOf(ColdTextLayer(id = 1), ColdTextLayer(id = 2), ColdTextLayer(id = 3)),
22 | )
23 | val expected =
24 | WidgetData(
25 | appWidgetId = 1,
26 | layers = listOf(ColdTextLayer(id = 1), ColdTextLayer(id = 2), ColdTextLayer(id = 3)),
27 | )
28 | val actual = widgetData.updateLayer(ColdTextLayer(id = 100, name = "invalid layer"))
29 | assertEquals(expected, actual)
30 | }
31 |
32 | @Test
33 | fun updateLayer_test1() {
34 | val widgetData =
35 | WidgetData(
36 | appWidgetId = 1,
37 | layers = listOf(ColdTextLayer(id = 1), ColdTextLayer(id = 2), ColdTextLayer(id = 3)),
38 | )
39 | val expected =
40 | WidgetData(
41 | appWidgetId = 1,
42 | layers =
43 | listOf(
44 | ColdTextLayer(id = 1),
45 | ColdTextLayer(id = 2, name = "updated layer"),
46 | ColdTextLayer(id = 3),
47 | ),
48 | )
49 | val actual = widgetData.updateLayer(ColdTextLayer(id = 2, name = "updated layer"))
50 | assertEquals(expected, actual)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/core/model/src/commonMain/kotlin/io/github/sadellie/sukko/core/model/modifier/ColumnAlignmentModifier.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.core.model.modifier
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.ui.Alignment
5 | import androidx.compose.ui.Modifier
6 | import co.touchlab.kermit.Logger
7 | import io.github.sadellie.sukko.core.model.Globals
8 | import io.github.sadellie.sukko.core.model.LayerContext
9 | import io.github.sadellie.sukko.core.model.basic.AlignmentSource
10 | import io.github.sadellie.sukko.resources.Res
11 | import io.github.sadellie.sukko.resources.core_model_modifier_column_alignment
12 | import kotlinx.serialization.Serializable
13 | import kotlinx.serialization.Transient
14 |
15 | @Serializable
16 | data class ColdColumnAlignmentModifier(
17 | override val id: Int,
18 | val alignmentSource: AlignmentSource.Horizontal = AlignmentSource.Start,
19 | ) : WidgetModifier.Cold {
20 | @Transient override val displayName = Res.string.core_model_modifier_column_alignment
21 |
22 | override suspend fun evaluate(layerContext: LayerContext, globals: Globals) =
23 | EvaluatedColumnAlignmentModifier(id = id, alignment = alignmentSource.getAlignment())
24 |
25 | override fun updateId(newId: Int) = this.copy(id = newId)
26 | }
27 |
28 | data class EvaluatedColumnAlignmentModifier(
29 | override val id: Int,
30 | val alignment: Alignment.Horizontal,
31 | ) : WidgetModifier.Evaluated {
32 | companion object {
33 | private const val TAG = "EvaluatedColumnAlignmentModifier"
34 | }
35 |
36 | override fun addToModifier(modifier: Modifier, scope: Any): Modifier {
37 | if (scope !is ColumnScope) {
38 | Logger.w(TAG) { "Wrong scope: $scope" }
39 | return modifier
40 | }
41 | return with(scope) { modifier.align(alignment) }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/feature/editor/src/commonMain/kotlin/io/github/sadellie/sukko/feature/editor/modifiers/EditorModifierFillMaxSize.kt:
--------------------------------------------------------------------------------
1 | package io.github.sadellie.sukko.feature.editor.modifiers
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.composables.core.SheetDetent
7 | import com.composables.core.rememberModalBottomSheetState
8 | import io.github.sadellie.sukko.core.model.basic.LocalScriptableDisplay
9 | import io.github.sadellie.sukko.core.model.modifier.ColdFillMaxSizeModifier
10 | import io.github.sadellie.sukko.core.model.modifier.ColdFillMaxWidthModifier
11 | import io.github.sadellie.sukko.core.ui.expand
12 | import io.github.sadellie.sukko.feature.editor.selector.DoubleSelectorSheet
13 | import org.jetbrains.compose.resources.stringResource
14 | import sh.calvin.reorderable.ReorderableCollectionItemScope
15 |
16 | @Composable
17 | internal fun ReorderableCollectionItemScope.EditorModifierFillMaxSize(
18 | modifier: Modifier,
19 | widgetModifier: ColdFillMaxSizeModifier,
20 | onUpdateModifier: (ColdFillMaxSizeModifier) -> Unit,
21 | state: EditorModifierListItemState,
22 | ) {
23 | val sheetState = rememberModalBottomSheetState(SheetDetent.Hidden)
24 | EditorModifierFlatListItem(
25 | modifier = modifier.clickable { sheetState.expand() },
26 | headlineText = stringResource(widgetModifier.displayName),
27 | supportingText = LocalScriptableDisplay.current.displayString(widgetModifier.fraction),
28 | state = state,
29 | )
30 |
31 | DoubleSelectorSheet(
32 | state = sheetState,
33 | onValueSelected = { onUpdateModifier(widgetModifier.copy(fraction = it)) },
34 | value = widgetModifier.fraction,
35 | range = ColdFillMaxWidthModifier.fractionRange,
36 | allowFraction = true,
37 | globals = state.globals.doubles,
38 | )
39 | }
40 |
--------------------------------------------------------------------------------