├── demo
├── .gitignore
├── src
│ ├── iosMain
│ │ └── kotlin
│ │ │ └── main.ios.kt
│ └── commonMain
│ │ └── kotlin
│ │ └── eu
│ │ └── wewox
│ │ └── lazytable
│ │ ├── ui
│ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Spacing.kt
│ │ │ └── Theme.kt
│ │ ├── extensions
│ │ │ └── StringExtensions.kt
│ │ └── components
│ │ │ └── TopBar.kt
│ │ ├── data
│ │ ├── Cell.kt
│ │ ├── PokemonDto.kt
│ │ └── Pokemon.kt
│ │ ├── Example.kt
│ │ ├── App.kt
│ │ ├── screens
│ │ ├── LazyTableSimpleScreen.kt
│ │ ├── LazyTableStateScreen.kt
│ │ └── LazyTablePinnedScreen.kt
│ │ └── RootScreen.kt
├── demo.podspec
└── build.gradle.kts
├── androiddemo
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ │ └── colors.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── drawable
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── eu
│ │ └── wewox
│ │ └── lazytable
│ │ └── MainActivity.kt
├── proguard-rules.pro
└── build.gradle.kts
├── desktopdemo
├── .gitignore
├── src
│ └── jvmMain
│ │ └── kotlin
│ │ └── Main.kt
└── build.gradle.kts
├── lazytable
├── .gitignore
├── src
│ ├── androidMain
│ │ └── AndroidManifest.xml
│ └── commonMain
│ │ └── kotlin
│ │ └── eu
│ │ └── wewox
│ │ └── lazytable
│ │ ├── LazyTableScrollDirection.kt
│ │ ├── LazyTableItem.kt
│ │ ├── LazyTablePinConfiguration.kt
│ │ ├── LazyTablePositionProvider.kt
│ │ ├── LazyTableScope.kt
│ │ ├── LazyTableDimensions.kt
│ │ ├── LazyTableState.kt
│ │ └── LazyTable.kt
└── build.gradle.kts
├── wasmdemo
├── .gitignore
├── src
│ └── wasmJsMain
│ │ ├── kotlin
│ │ └── eu
│ │ │ └── wewox
│ │ │ └── lazytable
│ │ │ └── Main.kt
│ │ └── resources
│ │ └── index.html
└── build.gradle.kts
├── .idea
├── .gitignore
├── compiler.xml
├── vcs.xml
├── kotlinc.xml
├── AndroidProjectSystem.xml
├── migrations.xml
├── misc.xml
├── gradle.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── iosdemo
├── Configuration
│ └── Config.xcconfig
├── iosApp
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── app-icon-512.png
│ │ │ └── Contents.json
│ │ └── AccentColor.colorset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── iOSApp.swift
│ ├── ContentView.swift
│ └── Info.plist
├── Podfile
├── iosApp.xcworkspace
│ └── contents.xcworkspacedata
└── iosApp.xcodeproj
│ └── project.pbxproj
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── cleanup.sh
├── settings.gradle.kts
├── .gitignore
├── .github
└── workflows
│ ├── static-analysis.yml
│ └── publish.yml
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
├── LICENSE.md
└── config
└── detekt
└── detekt.yml
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/androiddemo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/desktopdemo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lazytable/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/wasmdemo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/iosdemo/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=eu.wewox.lazytable
3 | APP_NAME=LazyTable
4 |
--------------------------------------------------------------------------------
/lazytable/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/androiddemo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LazyTable
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/iosdemo/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosdemo/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | platform :ios, '14.1'
4 |
5 | target 'iosApp' do
6 | pod 'demo', :path => '../demo'
7 | end
8 |
--------------------------------------------------------------------------------
/androiddemo/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/iosdemo/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/iosdemo/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct iOSApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ContentView()
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/androiddemo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/androiddemo/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/black
4 |
5 |
--------------------------------------------------------------------------------
/iosdemo/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oleksandrbalan/lazytable/HEAD/iosdemo/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-512.png
--------------------------------------------------------------------------------
/demo/src/iosMain/kotlin/main.ios.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import eu.wewox.lazytable.App
3 |
4 | fun MainViewController() = ComposeUIViewController { App() }
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/iosdemo/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/androiddemo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | @android:color/white
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.11.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/.idea/AndroidProjectSystem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | rm -rf .idea
3 | ./gradlew clean
4 | rm -rf .gradle
5 | rm -rf build
6 | rm -rf */build
7 | rm -rf iosdemo/iosApp.xcworkspace
8 | rm -rf iosdemo/Pods
9 | rm -rf iosdemo/iosApp.xcodeproj/project.xcworkspace
10 | rm -rf iosdemo/iosApp.xcodeproj/xcuserdata
11 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UndocumentedPublicProperty")
2 |
3 | package eu.wewox.lazytable.ui.theme
4 |
5 | import androidx.compose.ui.graphics.Color
6 |
7 | val LightBlue = Color(0xFF6DD3FF)
8 | val LightYellow = Color(0xFFFFF281)
9 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/ui/theme/Spacing.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.ui.theme
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | /**
6 | * The small spacing.
7 | */
8 | val SpacingSmall = 8.dp
9 |
10 | /**
11 | * The medium spacing.
12 | */
13 | val SpacingMedium = 16.dp
14 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/iosdemo/iosApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/iosdemo/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-512.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "512x512"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/androiddemo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/desktopdemo/src/jvmMain/kotlin/Main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.Window
2 | import androidx.compose.ui.window.application
3 | import eu.wewox.lazytable.App
4 |
5 | fun main() = application {
6 | Window(
7 | onCloseRequest = ::exitApplication,
8 | title = "LazyTable",
9 | ) {
10 | App()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/wasmdemo/src/wasmJsMain/kotlin/eu/wewox/lazytable/Main.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.ui.ExperimentalComposeUiApi
4 | import androidx.compose.ui.window.CanvasBasedWindow
5 |
6 | @OptIn(ExperimentalComposeUiApi::class)
7 | fun main() {
8 | CanvasBasedWindow("Wasm Demo", canvasElementId = "canvas") {
9 | App()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wasmdemo/src/wasmJsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Wasm Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/data/Cell.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.data
2 |
3 | /**
4 | * Creates data for lazy table.
5 | */
6 | fun createCells(columns: Int = COLUMNS, rows: Int = ROWS): List> =
7 | buildList {
8 | repeat(rows) { row ->
9 | repeat(columns) { column ->
10 | add(column to row)
11 | }
12 | }
13 | }
14 |
15 | private const val COLUMNS = 10
16 | private const val ROWS = 30
17 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/ui/extensions/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.ui.extensions
2 |
3 | import kotlin.math.pow
4 | import kotlin.math.roundToInt
5 |
6 | /**
7 | * Formats a float value to a string with defined number of decimal numbers.
8 | */
9 | fun Float.formatToDecimals(decimals: Int = 1): String {
10 | val integerDigits = this.toInt()
11 | val floatDigits = ((this - integerDigits) * 10f.pow(decimals)).roundToInt()
12 | return "$integerDigits.$floatDigits"
13 | }
14 |
--------------------------------------------------------------------------------
/iosdemo/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import demo
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | Main_iosKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/Example.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | /**
4 | * Enumeration of available demo examples.
5 | *
6 | * @param label Example name.
7 | * @param description Brief description.
8 | */
9 | enum class Example(
10 | val label: String,
11 | val description: String,
12 | ) {
13 | LazyTableSimple(
14 | "Simple Lazy Table",
15 | "Basic lazy table usage"
16 | ),
17 | LazyTableState(
18 | "Lazy Table State",
19 | "Example how lazy table state could be used"
20 | ),
21 | LazyTablePinned(
22 | "Pinned Items In Lazy Table",
23 | "Example how to setup pinned columns and rows"
24 | ),
25 | }
26 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | google()
5 | mavenCentral()
6 | gradlePluginPortal()
7 | }
8 | }
9 |
10 | plugins {
11 | id("org.gradle.toolchains.foojay-resolver-convention") version("0.7.0")
12 | }
13 |
14 | dependencyResolutionManagement {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
19 | mavenLocal()
20 | }
21 | }
22 |
23 | rootProject.name = "LazyTable"
24 | include(":demo")
25 | include(":lazytable")
26 | include(":desktopdemo")
27 | include(":androiddemo")
28 | include(":wasmdemo")
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/.name
5 | /.idea/artifacts
6 | /.idea/caches
7 | /.idea/libraries
8 | /.idea/modules.xml
9 | /.idea/workspace.xml
10 | /.idea/navEditor.xml
11 | /.idea/assetWizardSettings.xml
12 | /.idea/deploymentTargetDropDown.xml
13 | /.idea/compiler.xml
14 | /.idea/kotlinc.xml
15 | /.idea/vcs.xml
16 | /.idea/misc.xml
17 | /.idea/inspectionProfiles
18 | /.idea/shelf
19 | /.idea/deploymentTargetSelector.xml
20 | /.idea/runConfigurations.xml
21 | /.idea/studiobot.xml
22 | /.kotlin/metadata/*
23 | .DS_Store
24 | /build
25 | /captures
26 | .externalNativeBuild
27 | .cxx
28 | local.properties
29 | iosdemo/Podfile.lock
30 | iosdemo/Pods/*
31 | iosdemo/iosdemo.xcworkspace/*
32 | iosdemo/iosdemo.xcodeproj/*
33 | !iosdemo/iosdemo.xcodeproj/project.pbxproj
34 | demo/shared.podspec
35 | kotlin-js-store/
36 |
--------------------------------------------------------------------------------
/androiddemo/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/androiddemo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/desktopdemo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2 |
3 | plugins {
4 | alias(libs.plugins.kotlin.multiplatform)
5 | alias(libs.plugins.jetbrains.compose)
6 | alias(libs.plugins.compose.compiler)
7 | }
8 |
9 | kotlin {
10 | jvm()
11 | sourceSets {
12 | val jvmMain by getting {
13 | dependencies {
14 | implementation(project(":demo"))
15 | implementation(compose.desktop.currentOs)
16 | }
17 | }
18 | }
19 | }
20 |
21 | compose.desktop {
22 | application {
23 | mainClass = "MainKt"
24 | nativeDistributions {
25 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
26 | packageName = "lazytable"
27 | packageVersion = "1.0.0"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/androiddemo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTableScrollDirection.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import eu.wewox.minabox.MinaBoxScrollDirection
4 |
5 | /**
6 | * Determines which directions are allowed to scroll.
7 | */
8 | public enum class LazyTableScrollDirection {
9 | /**
10 | * Both horizontal and vertical scroll gestures are allowed.
11 | */
12 | BOTH,
13 |
14 | /**
15 | * Only horizontal scroll gestures are allowed, useful for LazyRow-ish layouts.
16 | */
17 | HORIZONTAL,
18 |
19 | /**
20 | * Only vertical scroll gestures are allowed, useful for LazyColumn-ish layouts.
21 | */
22 | VERTICAL
23 | }
24 |
25 | /**
26 | * Maps [LazyTableScrollDirection] to [MinaBoxScrollDirection].
27 | */
28 | internal fun LazyTableScrollDirection.toMinaBoxScrollDirection(): MinaBoxScrollDirection =
29 | when (this) {
30 | LazyTableScrollDirection.BOTH -> MinaBoxScrollDirection.BOTH
31 | LazyTableScrollDirection.HORIZONTAL -> MinaBoxScrollDirection.HORIZONTAL
32 | LazyTableScrollDirection.VERTICAL -> MinaBoxScrollDirection.VERTICAL
33 | }
34 |
--------------------------------------------------------------------------------
/wasmdemo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
2 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
3 |
4 | plugins {
5 | alias(libs.plugins.kotlin.multiplatform)
6 | alias(libs.plugins.jetbrains.compose)
7 | alias(libs.plugins.compose.compiler)
8 | }
9 |
10 | kotlin {
11 | @OptIn(ExperimentalWasmDsl::class)
12 | wasmJs {
13 | moduleName = "lazytable-demo"
14 | browser {
15 | commonWebpackConfig {
16 | outputFileName = "main.js"
17 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply {
18 | port = 8080
19 | static = (static ?: mutableListOf()).apply {
20 | add(project.rootDir.path)
21 | }
22 | }
23 | }
24 | }
25 | binaries.executable()
26 | }
27 | sourceSets {
28 | val wasmJsMain by getting {
29 | dependencies {
30 | implementation(project(":demo"))
31 | implementation(compose.ui)
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/androiddemo/src/main/kotlin/eu/wewox/lazytable/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.BackHandler
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.saveable.rememberSaveable
11 | import androidx.compose.runtime.setValue
12 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
13 |
14 | /**
15 | * Main activity for demo application.
16 | * Contains simple "Crossfade" based navigation to various examples.
17 | */
18 | class MainActivity : ComponentActivity() {
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | installSplashScreen()
22 | enableEdgeToEdge()
23 | super.onCreate(savedInstanceState)
24 |
25 | setContent {
26 | var example by rememberSaveable { mutableStateOf(null) }
27 | BackHandler(enabled = example != null) {
28 | example = null
29 | }
30 | App(
31 | example = example,
32 | onChangeExample = { example = it },
33 | )
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lazytable/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
2 |
3 | plugins {
4 | alias(libs.plugins.android.library)
5 | alias(libs.plugins.kotlin.multiplatform)
6 | alias(libs.plugins.jetbrains.compose)
7 | alias(libs.plugins.mavenpublish)
8 | alias(libs.plugins.compose.compiler)
9 | id("convention.jvm.toolchain")
10 | }
11 |
12 | kotlin {
13 | androidTarget()
14 |
15 | jvm()
16 |
17 | iosX64()
18 | iosArm64()
19 | iosSimulatorArm64()
20 |
21 | @OptIn(ExperimentalWasmDsl::class)
22 | wasmJs {
23 | browser()
24 | binaries.library()
25 | }
26 |
27 | applyDefaultHierarchyTemplate()
28 |
29 | sourceSets {
30 | val commonMain by getting {
31 | dependencies {
32 | api(compose.runtime)
33 | api(compose.foundation)
34 |
35 | api(libs.minabox)
36 | }
37 | }
38 | }
39 | }
40 |
41 | android {
42 | namespace = "eu.wewox.lazytable"
43 |
44 | compileSdk = libs.versions.sdk.compile.get().toInt()
45 |
46 | defaultConfig {
47 | minSdk = libs.versions.sdk.min.get().toInt()
48 | }
49 | buildFeatures {
50 | compose = true
51 | }
52 | kotlin {
53 | explicitApi()
54 |
55 | androidTarget {
56 | publishLibraryVariants("release", "debug")
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/.github/workflows/static-analysis.yml:
--------------------------------------------------------------------------------
1 | # This is a workflow to verify PRs with static code analysis tools
2 | name: Static Analysis
3 |
4 | # Controls when the workflow will run
5 | on:
6 | pull_request:
7 | branches: [main, develop]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | jobs:
13 | detekt:
14 | name: Detekt
15 | runs-on: macos-latest
16 |
17 | # Steps represent a sequence of tasks that will be executed as part of the job
18 | steps:
19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
20 | - uses: actions/checkout@v3
21 | - name: Set up JDK 17
22 | uses: actions/setup-java@v3
23 | with:
24 | java-version: '17'
25 | distribution: 'temurin'
26 |
27 | # Runs a single command using the runners shell
28 | - name: detekt
29 | run: ./gradlew detekt
30 |
31 | spotless:
32 | name: Spotless
33 | runs-on: macos-latest
34 |
35 | # Steps represent a sequence of tasks that will be executed as part of the job
36 | steps:
37 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
38 | - uses: actions/checkout@v3
39 | - name: Set up JDK 17
40 | uses: actions/setup-java@v3
41 | with:
42 | java-version: '17'
43 | distribution: 'temurin'
44 |
45 | # Runs a single command using the runners shell
46 | - name: spotless
47 | run: ./gradlew spotlessCheck
48 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/data/PokemonDto.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicProperty", "UndocumentedPublicFunction")
2 |
3 | package eu.wewox.lazytable.data
4 |
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class PokemonDto(
10 | val id: Int,
11 | val name: String,
12 | val height: Int,
13 | val weight: Int,
14 | val sprites: Sprites,
15 | val species: NameDto,
16 | val stats: List,
17 | ) {
18 | @Serializable
19 | data class Sprites(
20 | val other: Other
21 | ) {
22 | @Serializable
23 | data class Other(
24 | @SerialName("official-artwork")
25 | val officialArtwork: Images
26 | ) {
27 | @Serializable
28 | data class Images(
29 | @SerialName("front_default")
30 | val frontDefault: String?
31 | )
32 | }
33 | }
34 |
35 | @Serializable
36 | data class Stat(
37 | @SerialName("base_stat")
38 | val value: Int,
39 | @SerialName("stat")
40 | val name: NameDto
41 | )
42 |
43 | val imageUrl: String by lazy {
44 | sprites.other.officialArtwork.frontDefault.orEmpty()
45 | }
46 |
47 | fun stat(name: String): Int =
48 | stats.find { it.name.value == name }?.value ?: 0
49 | }
50 |
51 | @Serializable
52 | data class NameDto(
53 | @SerialName("name")
54 | val value: String
55 | )
56 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/App.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.saveable.rememberSaveable
8 | import androidx.compose.runtime.setValue
9 | import eu.wewox.lazytable.screens.LazyTablePinnedScreen
10 | import eu.wewox.lazytable.screens.LazyTableSimpleScreen
11 | import eu.wewox.lazytable.screens.LazyTableStateScreen
12 | import eu.wewox.lazytable.ui.theme.LazyTableTheme
13 |
14 | @Composable
15 | fun App() {
16 | var example by rememberSaveable { mutableStateOf(null) }
17 | App(
18 | example = example,
19 | onChangeExample = { example = it },
20 | )
21 | }
22 |
23 | @Composable
24 | fun App(
25 | example: Example?,
26 | onChangeExample: (Example?) -> Unit,
27 | ) {
28 | LazyTableTheme {
29 | val reset = { onChangeExample(null) }
30 |
31 | Crossfade(
32 | targetState = example,
33 | label = RootTransitionLabel
34 | ) { selected ->
35 | when (selected) {
36 | null -> RootScreen(onExampleClick = onChangeExample)
37 | Example.LazyTableSimple -> LazyTableSimpleScreen(reset)
38 | Example.LazyTableState -> LazyTableStateScreen(reset)
39 | Example.LazyTablePinned -> LazyTablePinnedScreen(reset)
40 | }
41 | }
42 | }
43 | }
44 |
45 | private const val RootTransitionLabel = "Root cross-fade"
46 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Publish To Maven Central
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow when tag is pushed
8 | push:
9 | tags:
10 | - 'v*'
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
16 | jobs:
17 | # This workflow contains a single job called "publish"
18 | publish:
19 | # The type of runner that the job will run on
20 | runs-on: macos-latest
21 |
22 | # Steps represent a sequence of tasks that will be executed as part of the job
23 | steps:
24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
25 | - uses: actions/checkout@v3
26 | - name: Set up JDK 17
27 | uses: actions/setup-java@v3
28 | with:
29 | java-version: '17'
30 | distribution: 'temurin'
31 |
32 | # Runs a single command using the runners shell
33 | - name: publish
34 | run: ./gradlew publish --no-daemon --no-parallel
35 | env:
36 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
37 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
38 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
39 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
40 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
41 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTableItem.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import eu.wewox.minabox.MinaBoxItem
4 |
5 | /**
6 | * The layout data for item inside lazy table.
7 | *
8 | * @property column The index of the column.
9 | * @property row The index of the row.
10 | * @property columnsCount The count of columns occupied by the item.
11 | * @property rowsCount The count of rows occupied by the item.
12 | */
13 | public class LazyTableItem(
14 | public val column: Int,
15 | public val row: Int,
16 | public val columnsCount: Int = 1,
17 | public val rowsCount: Int = 1,
18 | )
19 |
20 | /**
21 | * Maps lazy table layout data to [MinaBoxItem]s.
22 | */
23 | internal fun LazyTableItem.toMinaBoxItem(
24 | pinConfiguration: LazyTablePinConfiguration,
25 | dimensions: LazyTablePxDimensions,
26 | tableHeight: Float,
27 | ): MinaBoxItem =
28 | MinaBoxItem(
29 | x = dimensions.sumOfColumns(0 until column),
30 | y = if (pinConfiguration.footer && row == dimensions.rowsSize.size - 1) {
31 | tableHeight - dimensions.rowsSize[dimensions.rowsSize.size - 1]
32 | } else {
33 | dimensions.sumOfRows(0 until row)
34 | },
35 | width = dimensions.sumOfColumns(column until column + columnsCount),
36 | height = dimensions.sumOfRows(row until row + rowsCount),
37 | lockHorizontally = column < pinConfiguration.columns(row),
38 | lockVertically = if (pinConfiguration.footer && row == dimensions.rowsSize.size - 1) {
39 | true
40 | } else {
41 | row < pinConfiguration.rows(column)
42 | },
43 | )
44 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/ui/components/TopBar.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.ui.components
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.IconButton
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.material3.TopAppBar
10 | import androidx.compose.material3.TopAppBarDefaults
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 |
15 | /**
16 | * The reusable component for top bar.
17 | *
18 | * @param title The text to show in top bar.
19 | * @param modifier The modifier instance for the root composable.
20 | */
21 | @Composable
22 | fun TopBar(
23 | title: String,
24 | modifier: Modifier = Modifier,
25 | onBackClick: (() -> Unit)? = null,
26 | ) {
27 | TopAppBar(
28 | title = {
29 | Text(
30 | text = title,
31 | style = MaterialTheme.typography.titleLarge,
32 | )
33 | },
34 | navigationIcon = {
35 | if (onBackClick != null) {
36 | IconButton(onClick = onBackClick) {
37 | Icon(
38 | imageVector = Icons.AutoMirrored.Filled.ArrowBack,
39 | contentDescription = "Back button"
40 | )
41 | }
42 | }
43 | },
44 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors(Color.Transparent),
45 | modifier = modifier,
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTablePinConfiguration.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | /**
4 | * The configuration of pinned columns, rows, and footer.
5 | * Columns and rows are pinned from the beginning, then they are always displayed when user scrolls
6 | * in lazy table.
7 | *
8 | * @property columns The count of pinned columns for given row.
9 | * @property rows The count of pinned rows for given column.
10 | * @property footer The flag for showing a pinned footer.
11 | */
12 | @ConsistentCopyVisibility
13 | public data class LazyTablePinConfiguration internal constructor(
14 | val columns: (row: Int) -> Int,
15 | val rows: (column: Int) -> Int,
16 | val footer: Boolean,
17 | )
18 |
19 | /**
20 | * Creates configuration of pinned columns,rows, and footer.
21 | *
22 | * @param columns The count of pinned columns.
23 | * @param rows The count of pinned rows.
24 | * @param footer The flag for showing a pinned footer.
25 | */
26 | public fun lazyTablePinConfiguration(
27 | columns: Int = 0,
28 | rows: Int = 0,
29 | footer: Boolean = false,
30 | ): LazyTablePinConfiguration = LazyTablePinConfiguration({ columns }, { rows }, footer)
31 |
32 | /**
33 | * Creates configuration of pinned columns, rows, and footer.
34 | *
35 | * @param columns The count of pinned columns for given row.
36 | * @param rows The count of pinned rows for given column.
37 | * @param footer The flag for showing a pinned footer.
38 | */
39 | public fun lazyTablePinConfiguration(
40 | columns: (row: Int) -> Int = { 0 },
41 | rows: (column: Int) -> Int = { 0 },
42 | footer: Boolean = false
43 | ): LazyTablePinConfiguration = LazyTablePinConfiguration(columns, rows, footer)
44 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Typography
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.lightColorScheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.graphics.Color
10 |
11 | private val DarkColorScheme = darkColorScheme(
12 | primary = LightBlue,
13 | secondary = LightBlue,
14 | tertiary = LightYellow,
15 |
16 | onPrimary = Color.Black,
17 | onSecondary = Color.Black,
18 | onTertiary = Color.Black,
19 |
20 | surface = Color.Black,
21 | background = Color.Black,
22 |
23 | onSurface = Color.White,
24 | onBackground = Color.White,
25 | )
26 |
27 | private val LightColorScheme = lightColorScheme(
28 | primary = LightBlue,
29 | secondary = LightBlue,
30 | tertiary = LightYellow,
31 |
32 | onPrimary = Color.Black,
33 | onSecondary = Color.Black,
34 | onTertiary = Color.Black,
35 |
36 | surface = Color.White,
37 | background = Color.White,
38 |
39 | onSurface = Color.Black,
40 | onBackground = Color.Black,
41 | )
42 |
43 | /**
44 | * The theme to use for demo application.
45 | */
46 | @Composable
47 | fun LazyTableTheme(
48 | darkTheme: Boolean = isSystemInDarkTheme(),
49 | content: @Composable () -> Unit
50 | ) {
51 | val colorScheme = when {
52 | darkTheme -> DarkColorScheme
53 | else -> LightColorScheme
54 | }
55 |
56 | MaterialTheme(
57 | colorScheme = colorScheme,
58 | typography = Typography(),
59 | content = content
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/iosdemo/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/androiddemo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin)
4 | alias(libs.plugins.serialization)
5 | alias(libs.plugins.compose.compiler)
6 | id("convention.jvm.toolchain")
7 | }
8 |
9 | android {
10 | namespace = "eu.wewox.lazytable"
11 |
12 | compileSdk = libs.versions.sdk.compile.get().toInt()
13 |
14 | defaultConfig {
15 | applicationId = "eu.wewox.lazytable"
16 |
17 | minSdk = libs.versions.sdk.min.get().toInt()
18 | targetSdk = libs.versions.sdk.target.get().toInt()
19 |
20 | versionCode = 1
21 | versionName = "1.0"
22 |
23 | vectorDrawables {
24 | useSupportLibrary = true
25 | }
26 | }
27 |
28 | buildTypes {
29 | release {
30 | isMinifyEnabled = false
31 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
32 | }
33 | }
34 | buildFeatures {
35 | compose = true
36 | }
37 | packaging {
38 | resources {
39 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
40 | }
41 | }
42 | }
43 |
44 | dependencies {
45 | implementation(project(":lazytable"))
46 | implementation(project(":demo"))
47 |
48 | implementation(platform(libs.compose.bom))
49 | implementation(libs.compose.material3)
50 | implementation(libs.compose.ui)
51 | implementation(libs.compose.uitooling)
52 | implementation(libs.compose.uitoolingpreview)
53 | implementation(libs.androidx.activitycompose)
54 | implementation(libs.androidx.splashscreen)
55 | implementation(libs.ktor.android)
56 | implementation(libs.ktor.content.negotiation)
57 | implementation(libs.ktor.serialization.json)
58 | implementation(libs.ktx.serialization)
59 | }
60 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/screens/LazyTableSimpleScreen.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.screens
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Scaffold
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.Dp
16 | import eu.wewox.lazytable.Example
17 | import eu.wewox.lazytable.LazyTable
18 | import eu.wewox.lazytable.LazyTableItem
19 | import eu.wewox.lazytable.data.createCells
20 | import eu.wewox.lazytable.ui.components.TopBar
21 |
22 | /**
23 | * Basic lazy table usage.
24 | */
25 | @Composable
26 | fun LazyTableSimpleScreen(
27 | onBackClick: () -> Unit,
28 | ) {
29 | Scaffold(
30 | topBar = {
31 | TopBar(
32 | title = Example.LazyTableSimple.label,
33 | onBackClick = onBackClick
34 | )
35 | }
36 | ) { padding ->
37 | val columns = 10
38 | val rows = 30
39 | val cells = remember { createCells(columns, rows) }
40 |
41 | LazyTable(
42 | modifier = Modifier
43 | .fillMaxWidth()
44 | .padding(padding)
45 | ) {
46 | items(
47 | items = cells,
48 | layoutInfo = { LazyTableItem(it.first, it.second) }
49 | ) {
50 | Box(
51 | contentAlignment = Alignment.Center,
52 | modifier = Modifier
53 | .background(MaterialTheme.colorScheme.surface)
54 | .border(Dp.Hairline, MaterialTheme.colorScheme.onSurface)
55 | ) {
56 | Text(text = "$it")
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/RootScreen.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.foundation.lazy.items
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.Scaffold
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import eu.wewox.lazytable.ui.components.TopBar
20 | import eu.wewox.lazytable.ui.theme.SpacingMedium
21 |
22 | @Composable
23 | fun RootScreen(onExampleClick: (Example) -> Unit) {
24 | Scaffold(
25 | topBar = { TopBar("Lazy Table Demo") }
26 | ) { padding ->
27 | LazyColumn(Modifier.padding(padding)) {
28 | items(Example.entries) {
29 | Row(
30 | verticalAlignment = Alignment.CenterVertically,
31 | modifier = Modifier
32 | .fillMaxWidth()
33 | .clickable { onExampleClick(it) }
34 | .padding(SpacingMedium)
35 | ) {
36 | Column(modifier = Modifier.weight(1f)) {
37 | Text(
38 | text = it.label,
39 | style = MaterialTheme.typography.titleMedium
40 | )
41 | Text(
42 | text = it.description,
43 | style = MaterialTheme.typography.bodyMedium
44 | )
45 | }
46 | Icon(
47 | imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
48 | contentDescription = null
49 | )
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo/demo.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'demo'
3 | spec.version = '1.0.0'
4 | spec.homepage = '---'
5 | spec.source = { :http=> ''}
6 | spec.authors = ''
7 | spec.license = ''
8 | spec.summary = 'Demo Compose Multiplatform module'
9 | spec.vendored_frameworks = 'build/cocoapods/framework/demo.framework'
10 | spec.libraries = 'c++'
11 | spec.ios.deployment_target = '14.1'
12 |
13 |
14 | if !Dir.exist?('build/cocoapods/framework/demo.framework') || Dir.empty?('build/cocoapods/framework/demo.framework')
15 | raise "
16 |
17 | Kotlin framework 'demo' doesn't exist yet, so a proper Xcode project can't be generated.
18 | 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
19 |
20 | ./gradlew :demo:generateDummyFramework
21 |
22 | Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
23 | end
24 |
25 | spec.xcconfig = {
26 | 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
27 | }
28 |
29 | spec.pod_target_xcconfig = {
30 | 'KOTLIN_PROJECT_PATH' => ':demo',
31 | 'PRODUCT_MODULE_NAME' => 'demo',
32 | }
33 |
34 | spec.script_phases = [
35 | {
36 | :name => 'Build demo',
37 | :execution_position => :before_compile,
38 | :shell_path => '/bin/sh',
39 | :script => <<-SCRIPT
40 | if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
41 | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
42 | exit 0
43 | fi
44 | set -ev
45 | REPO_ROOT="$PODS_TARGET_SRCROOT"
46 | "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
47 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
48 | -Pkotlin.native.cocoapods.archs="$ARCHS" \
49 | -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
50 | SCRIPT
51 | }
52 | ]
53 | spec.resources = ['build/compose/cocoapods/compose-resources']
54 | end
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/screens/LazyTableStateScreen.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.screens
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Scaffold
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.rememberCoroutineScope
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.unit.Dp
18 | import eu.wewox.lazytable.Example
19 | import eu.wewox.lazytable.LazyTable
20 | import eu.wewox.lazytable.LazyTableItem
21 | import eu.wewox.lazytable.data.createCells
22 | import eu.wewox.lazytable.rememberSaveableLazyTableState
23 | import eu.wewox.lazytable.ui.components.TopBar
24 | import kotlinx.coroutines.launch
25 |
26 | /**
27 | * Example how lazy table state could be used.
28 | */
29 | @Composable
30 | fun LazyTableStateScreen(
31 | onBackClick: () -> Unit,
32 | ) {
33 | Scaffold(
34 | topBar = {
35 | TopBar(
36 | title = Example.LazyTableState.label,
37 | onBackClick = onBackClick
38 | )
39 | }
40 | ) { padding ->
41 | val columns = 10
42 | val rows = 30
43 | val cells = remember { createCells(columns, rows) }
44 |
45 | val scope = rememberCoroutineScope()
46 | val state = rememberSaveableLazyTableState()
47 |
48 | LazyTable(
49 | state = state,
50 | modifier = Modifier
51 | .fillMaxWidth()
52 | .padding(padding)
53 | ) {
54 | items(
55 | items = cells,
56 | layoutInfo = { LazyTableItem(it.first, it.second) }
57 | ) {
58 | Box(
59 | contentAlignment = Alignment.Center,
60 | modifier = Modifier
61 | .background(MaterialTheme.colorScheme.surface)
62 | .border(Dp.Hairline, MaterialTheme.colorScheme.onSurface)
63 | .clickable { scope.launch { state.animateToCell(it.first, it.second) } }
64 | ) {
65 | Text(text = "$it")
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
25 | # MPP
26 | kotlin.mpp.stability.nowarn=true
27 | kotlin.mpp.enableCInteropCommonization=true
28 | kotlin.mpp.androidSourceSetLayoutVersion=2
29 |
30 | # Compose
31 | org.jetbrains.compose.experimental.uikit.enabled=true
32 | org.jetbrains.compose.experimental.wasm.enabled=true
33 |
34 | # Maven setup with https://github.com/vanniktech/gradle-maven-publish-plugin
35 | SONATYPE_HOST=S01
36 | RELEASE_SIGNING_ENABLED=true
37 |
38 | GROUP=io.github.oleksandrbalan
39 | POM_ARTIFACT_ID=lazytable
40 | VERSION_NAME=1.10.0
41 |
42 | POM_NAME=Lazy Table
43 | POM_DESCRIPTION=Lazy layout for Jetpack Compose to display columns and rows of data on the two directional plane.
44 | POM_INCEPTION_YEAR=2023
45 | POM_URL=https://github.com/oleksandrbalan/lazytable
46 |
47 | POM_LICENSE_NAME=The Apache Software License, Version 2.0
48 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
49 | POM_LICENSE_DIST=repo
50 |
51 | POM_SCM_URL=https://github.com/oleksandrbalan/lazytable
52 | POM_SCM_CONNECTION=scm:git:git://github.com/oleksandrbalan/lazytable.git
53 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/oleksandrbalan/lazytable.git
54 |
55 | POM_DEVELOPER_ID=oleksandrbalan
56 | POM_DEVELOPER_NAME=Oleksandr Balan
57 | POM_DEVELOPER_URL=https://github.com/oleksandrbalan
58 |
--------------------------------------------------------------------------------
/demo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
2 |
3 | plugins {
4 | alias(libs.plugins.kotlin.multiplatform)
5 | alias(libs.plugins.jetbrains.cocoapods)
6 | alias(libs.plugins.jetbrains.compose)
7 | alias(libs.plugins.android.library)
8 | alias(libs.plugins.serialization)
9 | alias(libs.plugins.compose.compiler)
10 | id("convention.jvm.toolchain")
11 | }
12 |
13 | kotlin {
14 | androidTarget()
15 |
16 | jvm()
17 |
18 | iosX64()
19 | iosArm64()
20 | iosSimulatorArm64()
21 |
22 | @OptIn(ExperimentalWasmDsl::class)
23 | wasmJs {
24 | browser()
25 | binaries.library()
26 | }
27 |
28 | applyDefaultHierarchyTemplate()
29 |
30 | cocoapods {
31 | version = "1.0.0"
32 | summary = "Demo Compose Multiplatform module"
33 | homepage = "---"
34 | ios.deploymentTarget = "14.1"
35 | podfile = project.file("../iosdemo/Podfile")
36 | framework {
37 | baseName = "demo"
38 | isStatic = true
39 | }
40 | }
41 |
42 | sourceSets {
43 | val commonMain by getting {
44 | dependencies {
45 | implementation(project(":lazytable"))
46 |
47 | implementation(compose.material3)
48 |
49 | implementation(libs.ktor)
50 | implementation(libs.ktor.content.negotiation)
51 | implementation(libs.ktor.serialization.json)
52 | implementation(libs.ktx.serialization)
53 |
54 | implementation(libs.coil.compose)
55 | implementation(libs.coil.network.ktor3)
56 | }
57 | }
58 |
59 | val jvmMain by getting {
60 | dependencies {
61 | implementation(libs.ktor.okhttp)
62 | }
63 | }
64 |
65 | val androidMain by getting {
66 | dependencies {
67 | implementation(libs.compose.uitooling)
68 | implementation(libs.compose.uitoolingpreview)
69 | implementation(libs.ktor.android)
70 | }
71 | }
72 |
73 | val iosMain by getting {
74 | dependencies {
75 | implementation(libs.ktor.darwin)
76 | }
77 | }
78 |
79 | val wasmJsMain by getting {
80 | dependencies {
81 | implementation(libs.ktor.wasmJs)
82 | }
83 | }
84 |
85 | all {
86 | languageSettings.optIn("androidx.compose.material3.ExperimentalMaterial3Api")
87 | }
88 | }
89 | }
90 |
91 | android {
92 | namespace = "eu.wewox.lazytable.demo"
93 |
94 | compileSdk = libs.versions.sdk.compile.get().toInt()
95 | }
96 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/data/Pokemon.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UndocumentedPublicClass", "UndocumentedPublicProperty")
2 |
3 | package eu.wewox.lazytable.data
4 |
5 | import io.ktor.client.HttpClient
6 | import io.ktor.client.call.body
7 | import io.ktor.client.plugins.DefaultRequest
8 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
9 | import io.ktor.client.request.get
10 | import io.ktor.client.request.header
11 | import io.ktor.http.ContentType
12 | import io.ktor.http.HttpHeaders
13 | import io.ktor.serialization.kotlinx.json.json
14 | import kotlinx.serialization.json.Json
15 |
16 | /**
17 | * Pokemon domain model.
18 | */
19 | data class Pokemon(
20 | val id: Int,
21 | val number: String,
22 | val name: String,
23 | val height: Float,
24 | val weight: Float,
25 | val imageUrl: String,
26 | val stats: Stats,
27 | ) {
28 | data class Stats(
29 | val health: Int,
30 | val attack: Int,
31 | val defence: Int,
32 | val specialAttack: Int,
33 | val specialDefence: Int,
34 | val speed: Int,
35 | )
36 | }
37 |
38 | /**
39 | * Loads pokemons from remote.
40 | *
41 | * @param range The range of pokemon numbers.
42 | */
43 | suspend fun pokemons(range: IntRange = 1..30): List = range.map { id ->
44 | ktorHttpClient.get("https://pokeapi.co/api/v2/pokemon/$id/")
45 | .body()
46 | .let { dto ->
47 | Pokemon(
48 | id = id,
49 | number = pokemonNumber(id),
50 | name = dto.name,
51 | height = dto.height.toFloat() * 10, // convert to CM
52 | weight = dto.weight * 0.01f, // convert to KG
53 | imageUrl = dto.imageUrl,
54 | stats = Pokemon.Stats(
55 | health = dto.stat("hp"),
56 | attack = dto.stat("attack"),
57 | defence = dto.stat("defense"),
58 | specialAttack = dto.stat("special-attack"),
59 | specialDefence = dto.stat("special-defense"),
60 | speed = dto.stat("speed"),
61 | ),
62 | )
63 | }
64 | }
65 |
66 | internal val ktorHttpClient = HttpClient {
67 | install(ContentNegotiation) {
68 | json(
69 | Json {
70 | ignoreUnknownKeys = true
71 | }
72 | )
73 | }
74 |
75 | install(DefaultRequest) {
76 | header(HttpHeaders.ContentType, ContentType.Application.Json)
77 | }
78 | }
79 |
80 | private fun pokemonNumber(id: Int): String {
81 | val number = id.toString()
82 | val zeroes = (4 - number.length).coerceAtLeast(0)
83 | val prefix = List(zeroes) { "0" }.joinToString(separator = "")
84 | return "#$prefix$number"
85 | }
86 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | sdk-compile = "35"
3 | sdk-min = "21"
4 | sdk-target = "35"
5 |
6 | minabox = "1.10.0"
7 |
8 | # Must be updated along with compose-multiplatform
9 | compose-bom = "2025.01.00"
10 | compose-multiplatform = "1.7.3"
11 | activity-compose = "1.10.0"
12 | core-splashscreen = "1.0.1"
13 | coil = "3.1.0"
14 | ktor = "3.1.0"
15 | ktx-serialization = "1.8.0"
16 |
17 | plugin-android-gradle = "8.8.1"
18 | plugin-kotlin = "2.1.10"
19 | plugin-detekt = "1.23.5"
20 | plugin-spotless = "6.5.1"
21 | plugin-mavenpublish = "0.25.3"
22 |
23 | java-toolchain = "17"
24 |
25 | [libraries]
26 | minabox = { module = "io.github.oleksandrbalan:minabox", version.ref = "minabox" }
27 | compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
28 | compose-material3 = { module = "androidx.compose.material3:material3" }
29 | compose-ui = { module = "androidx.compose.ui:ui" }
30 | compose-uitooling = { module = "androidx.compose.ui:ui-tooling" }
31 | compose-uitoolingpreview = { module = "androidx.compose.ui:ui-tooling-preview" }
32 | androidx-activitycompose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
33 | androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "core-splashscreen" }
34 | coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
35 | coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
36 | ktor = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
37 | ktor-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
38 | ktor-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
39 | ktor-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
40 | ktor-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
41 | ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
42 | ktor-wasmJs = { module = "io.ktor:ktor-client-js-wasm-js", version.ref = "ktor" }
43 | ktx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "ktx-serialization" }
44 |
45 | [plugins]
46 | android-application = { id = "com.android.application", version.ref = "plugin-android-gradle" }
47 | android-library = { id = "com.android.library", version.ref = "plugin-android-gradle" }
48 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "plugin-kotlin" }
49 | jetbrains-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "plugin-kotlin" }
50 | jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
51 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "plugin-kotlin" }
52 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "plugin-kotlin" }
53 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "plugin-kotlin" }
54 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "plugin-detekt" }
55 | serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "plugin-kotlin" }
56 | spotless = { id = "com.diffplug.spotless", version.ref = "plugin-spotless" }
57 | mavenpublish = { id = "com.vanniktech.maven.publish", version.ref = "plugin-mavenpublish" }
58 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTablePositionProvider.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.ui.Alignment
4 | import androidx.compose.ui.geometry.Offset
5 | import androidx.compose.ui.unit.IntSize
6 | import eu.wewox.minabox.MinaBoxPositionProvider
7 |
8 | /**
9 | * Interface to provide offset of the registered lazy table items.
10 | */
11 | public interface LazyTablePositionProvider : MinaBoxPositionProvider {
12 | /**
13 | * Returns offset of the lazy table cell with [column] and [row].
14 | *
15 | * @param column The index of the column.
16 | * @param row The index of the row.
17 | * @param columnsCount The count of columns occupied by the item.
18 | * @param rowsCount The count of rows occupied by the item.
19 | * @param alignment The alignment to align item inside the [LazyTable].
20 | * @param currentX The current offset on the X axis.
21 | * @param currentY The current offset on the Y axis.
22 | * @return An item offset.
23 | */
24 | public fun getCellOffset(
25 | column: Int,
26 | row: Int,
27 | columnsCount: Int = 1,
28 | rowsCount: Int = 1,
29 | alignment: Alignment = Alignment.Center,
30 | currentX: Float = 0f,
31 | currentY: Float = 0f,
32 | ): Offset
33 | }
34 |
35 | /**
36 | * Implementation of the [LazyTablePositionProvider] with [MinaBoxPositionProvider].
37 | *
38 | * @property positionProvider The instance of the [MinaBoxPositionProvider].
39 | * @property dimensions The dimensions of the lazy table in pixels.
40 | * @property pinConfiguration The configuration of pinned columns and rows.
41 | */
42 | internal class LazyTablePositionProviderImpl(
43 | private val positionProvider: MinaBoxPositionProvider,
44 | private val dimensions: LazyTablePxDimensions,
45 | private val pinConfiguration: LazyTablePinConfiguration,
46 | ) : LazyTablePositionProvider, MinaBoxPositionProvider by positionProvider {
47 |
48 | override fun getCellOffset(
49 | column: Int,
50 | row: Int,
51 | columnsCount: Int,
52 | rowsCount: Int,
53 | alignment: Alignment,
54 | currentX: Float,
55 | currentY: Float,
56 | ): Offset {
57 | val itemOffset = Offset(
58 | x = dimensions.sumOfColumns(0 until column),
59 | y = dimensions.sumOfRows(0 until row),
60 | )
61 | val itemSize = IntSize(
62 | width = dimensions.sumOfColumns(column until column + columnsCount).toInt(),
63 | height = dimensions.sumOfRows(row until row + rowsCount).toInt(),
64 | )
65 |
66 | val columnPinConfiguration = pinConfiguration.columns(row)
67 | val rowPinConfiguration = pinConfiguration.rows(column)
68 |
69 | val paddingStart = dimensions.sumOfColumns(0 until columnPinConfiguration)
70 | val paddingTop = dimensions.sumOfRows(0 until rowPinConfiguration)
71 |
72 | val lockedHorizontally = column < columnPinConfiguration
73 | val lockedVertically = row < rowPinConfiguration
74 |
75 | val offset = align(itemSize, alignment, paddingStart, paddingTop)
76 |
77 | return Offset(
78 | x = if (lockedHorizontally) currentX else (itemOffset.x - offset.x - paddingStart),
79 | y = if (lockedVertically) currentY else (itemOffset.y - offset.y - paddingTop),
80 | )
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://central.sonatype.com/artifact/io.github.oleksandrbalan/lazytable)
2 |
3 |
4 |
5 | # Lazy Table
6 |
7 | Lazy table library for Compose UI.
8 |
9 | Lazy table allows to display columns and rows of data on the two directional plane. It is build on the [MinaBox](https://github.com/oleksandrbalan/minabox) (which is build on `LazyLayout`) and provides methods to register item(s) and handles scrolling on the plane.
10 |
11 | ## Multiplatform
12 |
13 | Library supports [Android](https://developer.android.com/jetpack/compose), [iOS](https://github.com/JetBrains/compose-multiplatform-ios-android-template/#readme), [Desktop](https://github.com/JetBrains/compose-multiplatform-desktop-template/#readme) (Windows, MacOS, Linux) and [Wasm](https://github.com/Kotlin/kotlin-wasm-examples/blob/main/compose-example/README.md#compose-multiplatform-for-web) targets.
14 |
15 | ## Usage
16 |
17 | ### Get a dependency
18 |
19 | **Step 1.** Add the MavenCentral repository to your build file.
20 | Add it in your root `build.gradle.kts` at the end of repositories:
21 | ```kotlin
22 | allprojects {
23 | repositories {
24 | ...
25 | mavenCentral()
26 | }
27 | }
28 | ```
29 |
30 | Or in `settings.gradle.kts`:
31 | ```kotlin
32 | dependencyResolutionManagement {
33 | repositories {
34 | ...
35 | mavenCentral()
36 | }
37 | }
38 | ```
39 |
40 | **Step 2.** Add the dependency.
41 | Check latest version on the [releases page](https://github.com/oleksandrbalan/lazytable/releases).
42 | ```kotlin
43 | dependencies {
44 | implementation("io.github.oleksandrbalan:lazytable:$version")
45 | }
46 | ```
47 |
48 | ### Use in Composable
49 |
50 | The core element of the `LazyTable` layout is a `content` lambda, where items are registered in the similar manner as in `LazyColumn` or `LazyRow`. The main difference is that each item must provide its position and size in the `layoutInfo` lambda. Each item defines its position by specifying the `column` and `row` and size by specifying how much columns or rows it occupies (by default is set to 1).
51 |
52 | The size of the cells are defined via `dimensions` parameter. There are multiple `lazyTableDimensions` methods which creates the dimensions for all or each column / row.
53 |
54 | Lazy table also allows to specify how many columns / rows should be pinned and thus be visible when user navigates in the table. Check the `pinConfiguration` parameter.
55 |
56 | It is also possible to observe on the scroll state and change it programmatically using an instance of the `LazyTableState`.
57 |
58 | ```kotlin
59 | val columns = 10
60 | val rows = 10
61 | LazyTable(
62 | dimensions = lazyTableDimensions(48.dp, 32.dp)
63 | ) {
64 | items(
65 | count = columns * rows,
66 | layoutInfo = {
67 | LazyTableItem(
68 | column = it % columns,
69 | row = it / columns,
70 | )
71 | }
72 | ) { index ->
73 | Text(text = "#$index")
74 | }
75 | }
76 | ```
77 |
78 | See Demo application and [examples](demo/src/commonMain/kotlin/eu/wewox/lazytable/screens) for more usage examples.
79 |
80 | ## Examples
81 |
82 | Simple table with items.
83 |
84 | https://github.com/oleksandrbalan/lazytable/assets/20944869/0277522f-b21a-4c93-b71f-9a1af2d079cc
85 |
86 | Advanced example with pinned columns and rows.
87 |
88 | https://github.com/oleksandrbalan/lazytable/assets/20944869/1a1c6bee-25cd-4cb5-b619-988fbe30554c
89 |
90 | If you need further customization options, check [MinaBox](https://github.com/oleksandrbalan/minabox) library.
91 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTableScope.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.lazy.layout.IntervalList
5 | import androidx.compose.foundation.lazy.layout.MutableIntervalList
6 | import androidx.compose.runtime.Composable
7 |
8 | /**
9 | * Receiver scope which is used by [LazyTable].
10 | */
11 | public interface LazyTableScope {
12 |
13 | /**
14 | * Adds a [count] of items.
15 | *
16 | * @param count The items count.
17 | * @param layoutInfo The lambda to provide layout information of the single item.
18 | * @param key A factory of stable and unique keys representing the item. Using the same key
19 | * for multiple items is not allowed. Type of the key should be saveable via Bundle on Android.
20 | * If null is passed the position in the list will represent the key.
21 | * @param contentType A factory of the content types for the item. The item compositions of
22 | * the same type could be reused more efficiently. Note that null is a valid type and items of
23 | * such type will be considered compatible.
24 | * @param itemContent The content displayed by a single item.
25 | */
26 | public fun items(
27 | count: Int,
28 | layoutInfo: (index: Int) -> LazyTableItem,
29 | key: ((index: Int) -> Any)? = null,
30 | contentType: (index: Int) -> Any? = { null },
31 | itemContent: @Composable (index: Int) -> Unit
32 | )
33 |
34 | /**
35 | * Adds given [items].
36 | *
37 | * @param items The items to add to the [LazyTable].
38 | * @param layoutInfo The lambda to provide layout information of the single item.
39 | * @param key A factory of stable and unique keys representing the item. Using the same key
40 | * for multiple items is not allowed. Type of the key should be saveable via Bundle on Android.
41 | * If null is passed the position in the list will represent the key.
42 | * @param contentType A factory of the content types for the item. The item compositions of
43 | * the same type could be reused more efficiently. Note that null is a valid type and items of
44 | * such type will be considered compatible.
45 | * @param itemContent The content displayed by a single item.
46 | */
47 | public fun items(
48 | items: List,
49 | layoutInfo: (item: T) -> LazyTableItem,
50 | key: ((item: T) -> Any)? = null,
51 | contentType: (item: T) -> Any? = { null },
52 | itemContent: @Composable (item: T) -> Unit
53 | ): Unit = items(
54 | count = items.size,
55 | layoutInfo = { index: Int -> layoutInfo(items[index]) },
56 | key = if (key != null) { index: Int -> key(items[index]) } else null,
57 | contentType = { index: Int -> contentType(items[index]) },
58 | itemContent = { index: Int -> itemContent(items[index]) },
59 | )
60 | }
61 |
62 | /**
63 | * Implementation of the [LazyTableScope].
64 | */
65 | @OptIn(ExperimentalFoundationApi::class)
66 | internal class LazyTableScopeImpl : LazyTableScope {
67 |
68 | private val _intervals = MutableIntervalList()
69 |
70 | /**
71 | * Registered items in the [LazyTable].
72 | */
73 | val intervals: IntervalList = _intervals
74 |
75 | override fun items(
76 | count: Int,
77 | layoutInfo: (index: Int) -> LazyTableItem,
78 | key: ((index: Int) -> Any)?,
79 | contentType: (index: Int) -> Any?,
80 | itemContent: @Composable (index: Int) -> Unit
81 | ) {
82 | _intervals.addInterval(
83 | count,
84 | LazyTableItemContent(layoutInfo, key, contentType, itemContent)
85 | )
86 | }
87 | }
88 |
89 | internal class LazyTableItemContent(
90 | val layoutInfo: (index: Int) -> LazyTableItem,
91 | val key: ((index: Int) -> Any)?,
92 | val contentType: (index: Int) -> Any?,
93 | val itemContent: @Composable (index: Int) -> Unit
94 | )
95 |
--------------------------------------------------------------------------------
/androiddemo/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
13 |
18 |
23 |
24 |
25 |
28 |
31 |
34 |
37 |
40 |
43 |
46 |
49 |
52 |
55 |
58 |
61 |
62 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTableDimensions.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.ui.unit.Density
4 | import androidx.compose.ui.unit.Dp
5 |
6 | /**
7 | * The dimensions of [LazyTable].
8 | */
9 | public sealed interface LazyTableDimensions {
10 |
11 | /**
12 | * Defines exact count of columns and rows, and their sizes (widths & heights).
13 | * Registered cells must be in this range.
14 | *
15 | * @property columnsSize The list of column sizes (widths).
16 | * @property rowsSize The list of row sizes (heights).
17 | */
18 | @ConsistentCopyVisibility
19 | public data class Exact internal constructor(
20 | public val columnsSize: List,
21 | public val rowsSize: List,
22 | ) : LazyTableDimensions
23 |
24 | /**
25 | * Defines the lambdas which provides sizes (widths & heights) for columns and rows.
26 | * The size of [LazyTable] is driven by registered items.
27 | *
28 | * @property columnSize The lambda to provide the size of column based on its index.
29 | * @property rowSize The lambda to provide the size of row based on its index.
30 | */
31 | @ConsistentCopyVisibility
32 | public data class Dynamic internal constructor(
33 | public val columnSize: (Int) -> Dp,
34 | public val rowSize: (Int) -> Dp,
35 | ) : LazyTableDimensions
36 | }
37 |
38 | /**
39 | * Creates dynamic lazy table dimensions.
40 | *
41 | * @param columnSize The size of each column.
42 | * @param rowSize The size of each row.
43 | * @see LazyTableDimensions.Dynamic
44 | * @return The dimensions of [LazyTable].
45 | */
46 | public fun lazyTableDimensions(columnSize: Dp, rowSize: Dp): LazyTableDimensions =
47 | LazyTableDimensions.Dynamic({ columnSize }, { rowSize })
48 |
49 | /**
50 | * Creates dynamic lazy table dimensions.
51 | *
52 | * @param columnSize The lambda to provide the size of column based on its index.
53 | * @param rowSize The lambda to provide the size of row based on its index.
54 | * @see LazyTableDimensions.Dynamic
55 | * @return The dimensions of [LazyTable].
56 | */
57 | public fun lazyTableDimensions(columnSize: (Int) -> Dp, rowSize: (Int) -> Dp): LazyTableDimensions =
58 | LazyTableDimensions.Dynamic(columnSize, rowSize)
59 |
60 | /**
61 | * Creates exact lazy table dimensions.
62 | *
63 | * @param columnsSize The list of column sizes (widths).
64 | * @param rowsSize The list of row sizes (heights).
65 | * @see LazyTableDimensions.Exact
66 | * @return The dimensions of [LazyTable].
67 | */
68 | public fun lazyTableDimensions(columnsSize: List, rowsSize: List): LazyTableDimensions =
69 | LazyTableDimensions.Exact(columnsSize, rowsSize)
70 |
71 | /**
72 | * Creates exact lazy table dimensions.
73 | *
74 | * @param columnsCount The count of columns.
75 | * @param rowsCount The count of rows.
76 | * @param columnSize The lambda to provide the size of column based on its index.
77 | * @param rowSize The lambda to provide the size of row based on its index.
78 | * @see LazyTableDimensions.Exact
79 | * @return The dimensions of [LazyTable].
80 | */
81 | public fun lazyTableDimensions(
82 | columnsCount: Int,
83 | rowsCount: Int,
84 | columnSize: (Int) -> Dp,
85 | rowSize: (Int) -> Dp,
86 | ): LazyTableDimensions =
87 | LazyTableDimensions.Exact(List(columnsCount, columnSize), List(rowsCount, rowSize))
88 |
89 | /**
90 | * Converts [LazyTableDimensions] to [LazyTablePxDimensions].
91 | */
92 | internal fun LazyTableDimensions.roundToPx(
93 | columns: Int,
94 | rows: Int,
95 | density: Density
96 | ): LazyTablePxDimensions =
97 | with(density) {
98 | when (val dimensions = this@roundToPx) {
99 | is LazyTableDimensions.Exact -> {
100 | require(
101 | dimensions.columnsSize.size >= columns && dimensions.rowsSize.size >= rows
102 | ) {
103 | "Unable to get sizes for all columns and rows. " +
104 | "Ensure that both columns count and rows count is equal or greater " +
105 | "than number of columns and rows in the scope. " +
106 | "Columns count: ${dimensions.columnsSize.size}, " +
107 | "columns in items: $columns; " +
108 | "rows count: ${dimensions.rowsSize.size}, " +
109 | "rows in items: $rows."
110 | }
111 |
112 | LazyTablePxDimensions(
113 | columnsSize = dimensions.columnsSize.map { it.toPx() },
114 | rowsSize = dimensions.rowsSize.map { it.toPx() },
115 | )
116 | }
117 |
118 | is LazyTableDimensions.Dynamic -> {
119 | LazyTablePxDimensions(
120 | columnsSize = List(columns) { dimensions.columnSize(it).toPx() },
121 | rowsSize = List(rows) { dimensions.rowSize(it).toPx() },
122 | )
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * The dimensions of the lazy table in pixels.
129 | */
130 | internal data class LazyTablePxDimensions(
131 | val columnsSize: List,
132 | val rowsSize: List,
133 | ) {
134 | fun sumOfColumns(range: IntRange): Float = columnsSize.sumOf(range)
135 |
136 | fun sumOfRows(range: IntRange): Float = rowsSize.sumOf(range)
137 |
138 | private fun List.sumOf(range: IntRange): Float =
139 | range.sumOf { get(it).toDouble() }.toFloat()
140 | }
141 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTableState.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Stable
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.runtime.saveable.Saver
7 | import androidx.compose.runtime.saveable.listSaver
8 | import androidx.compose.runtime.saveable.rememberSaveable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.geometry.Offset
11 | import eu.wewox.minabox.MinaBoxState
12 |
13 | /**
14 | * Creates a [LazyTableState] that is remembered across compositions.
15 | *
16 | * @param initialOffset The lambda to provide initial offset on the plane.
17 | * @return Instance of the [LazyTableState].
18 | */
19 | @Deprecated(
20 | message = "Use rememberSaveableLazyTableState() which uses rememberSaveable API.",
21 | replaceWith = ReplaceWith(
22 | "rememberSaveableLazyTableState(initialOffset)",
23 | "eu.wewox.lazytable.rememberSaveableLazyTableState"
24 | )
25 | )
26 | @Composable
27 | public fun rememberLazyTableState(
28 | initialOffset: LazyTablePositionProvider.() -> Offset = { Offset.Zero }
29 | ): LazyTableState {
30 | return remember { LazyTableState(initialOffset) }
31 | }
32 |
33 | /**
34 | * Creates a [LazyTableState] that is remembered across compositions and saved across activity or process recreation.
35 | *
36 | * @param initialOffset The lambda to provide initial offset on the plane.
37 | * @return Instance of the [LazyTableState].
38 | */
39 | @Composable
40 | public fun rememberSaveableLazyTableState(
41 | initialOffset: LazyTablePositionProvider.() -> Offset = { Offset.Zero },
42 | ): LazyTableState {
43 | return rememberSaveable(
44 | saver = LazyTableState.Saver(),
45 | init = { LazyTableState(initialOffset) }
46 | )
47 | }
48 |
49 | /**
50 | * A state object that can be hoisted to control and observe scrolling.
51 | *
52 | * @property initialOffset The lambda to provide initial offset on the plane.
53 | */
54 | @Stable
55 | public class LazyTableState(
56 | internal val initialOffset: LazyTablePositionProvider.() -> Offset
57 | ) {
58 | internal lateinit var dimensions: LazyTablePxDimensions
59 | internal lateinit var pinConfiguration: LazyTablePinConfiguration
60 |
61 | private val translateX: Float get() = minaBoxState.translate?.x ?: 0f
62 |
63 | private val translateY: Float get() = minaBoxState.translate?.y ?: 0f
64 |
65 | /**
66 | * The underlying [MinaBoxState] used to this state.
67 | */
68 | public val minaBoxState: MinaBoxState = MinaBoxState(
69 | initialOffset = {
70 | initialOffset(LazyTablePositionProviderImpl(this, dimensions, pinConfiguration))
71 | }
72 | )
73 |
74 | /**
75 | * The position provider used to get items offsets.
76 | */
77 | public val positionProvider: LazyTablePositionProvider
78 | get() = LazyTablePositionProviderImpl(
79 | positionProvider = minaBoxState.positionProvider,
80 | dimensions = dimensions,
81 | pinConfiguration = pinConfiguration
82 | )
83 |
84 | /**
85 | * Animates current offset to the cell with [column] and [row].
86 | *
87 | * @param column The index of the column.
88 | * @param row The index of the row.
89 | * @param columnsCount The count of columns occupied by the item.
90 | * @param rowsCount The count of rows occupied by the item.
91 | * @param alignment The alignment to align item inside the [LazyTable].
92 | */
93 | public suspend fun animateToCell(
94 | column: Int,
95 | row: Int,
96 | columnsCount: Int = 1,
97 | rowsCount: Int = 1,
98 | alignment: Alignment = Alignment.Center,
99 | ) {
100 | val offset = getCellOffset(column, row, columnsCount, rowsCount, alignment)
101 | minaBoxState.animateTo(offset.x, offset.y)
102 | }
103 |
104 | /**
105 | * Snaps current offset to the cell with [column] and [row].
106 | *
107 | * @param column The index of the column.
108 | * @param row The index of the row.
109 | * @param columnsCount The count of columns occupied by the item.
110 | * @param rowsCount The count of rows occupied by the item.
111 | * @param alignment The alignment to align item inside the [LazyTable].
112 | */
113 | public suspend fun snapToCell(
114 | column: Int,
115 | row: Int,
116 | columnsCount: Int = 1,
117 | rowsCount: Int = 1,
118 | alignment: Alignment = Alignment.Center
119 | ) {
120 | val offset = getCellOffset(column, row, columnsCount, rowsCount, alignment)
121 | minaBoxState.snapTo(offset.x, offset.y)
122 | }
123 |
124 | private fun getCellOffset(
125 | column: Int,
126 | row: Int,
127 | columnsCount: Int,
128 | rowsCount: Int,
129 | alignment: Alignment
130 | ): Offset =
131 | positionProvider.getCellOffset(
132 | column = column,
133 | row = row,
134 | columnsCount = columnsCount,
135 | rowsCount = rowsCount,
136 | alignment = alignment,
137 | currentX = translateX,
138 | currentY = translateY,
139 | )
140 |
141 | internal companion object {
142 |
143 | /**
144 | * Creates a [Saver] that can save and restore a [LazyTableState].
145 | *
146 | * @return A [Saver] instance for saving and restoring [LazyTableState].
147 | */
148 | fun Saver(): Saver = listSaver(
149 | save = {
150 | listOf(
151 | it.minaBoxState.translate?.x ?: 0f,
152 | it.minaBoxState.translate?.y ?: 0f,
153 | )
154 | },
155 | restore = {
156 | LazyTableState {
157 | Offset(it[0], it[1])
158 | }
159 | }
160 | )
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/lazytable/src/commonMain/kotlin/eu/wewox/lazytable/LazyTable.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.layout.onSizeChanged
11 | import androidx.compose.ui.platform.LocalDensity
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.zIndex
14 | import eu.wewox.minabox.MinaBox
15 | import eu.wewox.minabox.MinaBoxItem
16 | import kotlin.math.max
17 | import kotlin.math.min
18 |
19 | /**
20 | * Lazy layout to display columns and rows of data on the two directional plane.
21 | * Items should be provided with [content] lambda.
22 | *
23 | * @param modifier The modifier instance for the root composable.
24 | * @param state The state which could be used to observe and change translation offset.
25 | * @param pinConfiguration The configuration of pinned columns and rows.
26 | * @param dimensions The dimensions of columns and rows.
27 | * @param contentPadding A padding around the whole content. This will add padding for the content
28 | * after it has been clipped, which is not possible via modifier param.
29 | * @param scrollDirection Determines which directions are allowed to scroll.
30 | * @param content The lambda block which describes the content. Inside this block you can use
31 | * [LazyTableScope.items] method to add items.
32 | */
33 | @OptIn(ExperimentalFoundationApi::class)
34 | @Composable
35 | public fun LazyTable(
36 | modifier: Modifier = Modifier,
37 | state: LazyTableState = rememberSaveableLazyTableState(),
38 | pinConfiguration: LazyTablePinConfiguration = LazyTableDefaults.pinConfiguration(),
39 | dimensions: LazyTableDimensions = LazyTableDefaults.dimensions(),
40 | contentPadding: PaddingValues = PaddingValues(0.dp),
41 | scrollDirection: LazyTableScrollDirection = LazyTableScrollDirection.BOTH,
42 | content: LazyTableScope.() -> Unit
43 | ) {
44 | val scope = LazyTableScopeImpl().apply(content)
45 |
46 | val density = LocalDensity.current
47 | val (columnsCount, rowsCount) = scope.getItemsDimensions()
48 | val dimensionsPx = dimensions.roundToPx(columnsCount, rowsCount, density)
49 | val tableHeight = remember { mutableStateOf(0f) }
50 |
51 | state.dimensions = dimensionsPx
52 | state.pinConfiguration = pinConfiguration
53 |
54 | MinaBox(
55 | state = state.minaBoxState,
56 | contentPadding = contentPadding,
57 | scrollDirection = scrollDirection.toMinaBoxScrollDirection(),
58 | modifier = modifier.onSizeChanged {
59 | tableHeight.value = it.height.toFloat()
60 | },
61 | ) {
62 | val minTableHeight = min(tableHeight.value, dimensionsPx.rowsSize.sum())
63 |
64 | scope.intervals.forEach { interval ->
65 | items(
66 | count = interval.size,
67 | layoutInfo = {
68 | val info = interval.value.layoutInfo(it)
69 | info.toMinaBoxItem(pinConfiguration, dimensionsPx, minTableHeight)
70 | },
71 | key = interval.value.key,
72 | contentType = interval.value.contentType,
73 | itemContent = {
74 | // Show pinned cells with higher Z index to draw over others
75 | val index = interval.value.layoutInfo(it).getZIndex(pinConfiguration)
76 | Box(
77 | propagateMinConstraints = true,
78 | modifier = Modifier.zIndex(index),
79 | ) {
80 | interval.value.itemContent.invoke(it)
81 | }
82 | },
83 | )
84 | }
85 |
86 | if (pinConfiguration.footer) {
87 | // Empty content to push the second to last row up above the footer.
88 | items(
89 | count = 1,
90 | layoutInfo = {
91 | MinaBoxItem(
92 | x = 0f,
93 | y = dimensionsPx.rowsSize.sum() - dimensionsPx.rowsSize.last(),
94 | width = dimensionsPx.columnsSize.last(),
95 | height = dimensionsPx.rowsSize.last(),
96 | )
97 | },
98 | itemContent = {
99 | }
100 | )
101 | }
102 | }
103 | }
104 |
105 | @OptIn(ExperimentalFoundationApi::class)
106 | private fun LazyTableScopeImpl.getItemsDimensions(): Pair {
107 | var columns = 0
108 | var rows = 0
109 |
110 | intervals.forEach { interval ->
111 | repeat(interval.size) {
112 | val info = interval.value.layoutInfo(it)
113 | columns = max(columns, info.column + info.columnsCount)
114 | rows = max(rows, info.row + info.rowsCount)
115 | }
116 | }
117 |
118 | return columns to rows
119 | }
120 |
121 | private fun LazyTableItem.getZIndex(pinConfiguration: LazyTablePinConfiguration): Float {
122 | val pinnedColumn = column < pinConfiguration.columns(row)
123 | val pinnedRow = row < pinConfiguration.rows(column)
124 | val pinnedFooter = rowsCount.minus(row) >= 1 && pinConfiguration.footer // Last row and configuration footer is set
125 |
126 | return if (pinnedColumn && pinnedRow) {
127 | ZIndexPinnedCorner
128 | } else if (pinnedColumn && pinnedFooter) {
129 | ZIndexPinnedFooter
130 | } else if (pinnedColumn || pinnedRow || pinnedFooter) {
131 | ZIndexPinnedLine
132 | } else {
133 | ZIndexItem
134 | }
135 | }
136 |
137 | /**
138 | * Contains the default values for [LazyTable].
139 | */
140 | public object LazyTableDefaults {
141 |
142 | /**
143 | * The default dimensions of the lazy table.
144 | */
145 | public fun dimensions(): LazyTableDimensions =
146 | lazyTableDimensions(
147 | columnSize = { DefaultColumnSize },
148 | rowSize = { DefaultRowSize }
149 | )
150 |
151 | /**
152 | * The default configuration of pinned columns and rows.
153 | * By default nothing is pinned.
154 | */
155 | public fun pinConfiguration(): LazyTablePinConfiguration =
156 | lazyTablePinConfiguration(columns = 0, rows = 0)
157 | }
158 |
159 | private val DefaultColumnSize = 96.dp
160 | private val DefaultRowSize = 48.dp
161 |
162 | private const val ZIndexPinnedFooter = 3f
163 | private const val ZIndexPinnedCorner = 2f
164 | private const val ZIndexPinnedLine = 1f
165 | private const val ZIndexItem = 0f
166 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/eu/wewox/lazytable/screens/LazyTablePinnedScreen.kt:
--------------------------------------------------------------------------------
1 | package eu.wewox.lazytable.screens
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.material3.CircularProgressIndicator
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.Scaffold
15 | import androidx.compose.material3.Switch
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.getValue
19 | import androidx.compose.runtime.mutableStateOf
20 | import androidx.compose.runtime.produceState
21 | import androidx.compose.runtime.remember
22 | import androidx.compose.runtime.setValue
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.unit.Dp
26 | import androidx.compose.ui.unit.dp
27 | import coil3.compose.AsyncImage
28 | import coil3.compose.LocalPlatformContext
29 | import coil3.request.ImageRequest
30 | import coil3.request.crossfade
31 | import eu.wewox.lazytable.Example
32 | import eu.wewox.lazytable.LazyTable
33 | import eu.wewox.lazytable.LazyTableDimensions
34 | import eu.wewox.lazytable.LazyTableItem
35 | import eu.wewox.lazytable.LazyTablePinConfiguration
36 | import eu.wewox.lazytable.data.Pokemon
37 | import eu.wewox.lazytable.data.pokemons
38 | import eu.wewox.lazytable.lazyTableDimensions
39 | import eu.wewox.lazytable.lazyTablePinConfiguration
40 | import eu.wewox.lazytable.ui.components.TopBar
41 | import eu.wewox.lazytable.ui.extensions.formatToDecimals
42 | import eu.wewox.lazytable.ui.theme.SpacingMedium
43 |
44 | /**
45 | * Example how to setup pinned columns and rows.
46 | */
47 | @Composable
48 | fun LazyTablePinnedScreen(
49 | onBackClick: () -> Unit,
50 | ) {
51 | Scaffold(
52 | topBar = {
53 | TopBar(
54 | title = Example.LazyTablePinned.label,
55 | onBackClick = onBackClick
56 | )
57 | }
58 | ) { padding ->
59 | val columns = 11
60 | val pokemons = produceState(initialValue = emptyList()) { value = pokemons() }
61 |
62 | Column(
63 | verticalArrangement = Arrangement.Center,
64 | horizontalAlignment = Alignment.CenterHorizontally,
65 | modifier = Modifier
66 | .fillMaxSize()
67 | .padding(padding)
68 | ) {
69 | var settings by remember { mutableStateOf(Settings()) }
70 |
71 | if (pokemons.value.isEmpty()) {
72 | CircularProgressIndicator()
73 | } else {
74 | Settings(
75 | settings = settings,
76 | onChange = { settings = it },
77 | )
78 |
79 | LazyTable(
80 | pinConfiguration = pinConfiguration(settings),
81 | dimensions = dimensions(settings),
82 | modifier = Modifier.fillMaxSize()
83 | ) {
84 | items(
85 | count = pokemons.value.size * columns,
86 | layoutInfo = {
87 | LazyTableItem(
88 | column = it % columns,
89 | row = it / columns + if (settings.showHeader) 1 else 0,
90 | )
91 | },
92 | ) {
93 | Cell(pokemon = pokemons.value[it / columns], column = it % columns)
94 | }
95 |
96 | if (settings.showHeader) {
97 | items(
98 | count = columns,
99 | layoutInfo = {
100 | LazyTableItem(
101 | column = it % columns,
102 | row = 0,
103 | )
104 | },
105 | ) {
106 | HeaderCell(column = it)
107 | }
108 | }
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | private fun dimensions(settings: Settings): LazyTableDimensions =
116 | lazyTableDimensions(
117 | columnSize = {
118 | when (it) {
119 | 0 -> 148.dp
120 | 1 -> 48.dp
121 | else -> 96.dp
122 | }
123 | },
124 | rowSize = {
125 | if (it == 0 && settings.showHeader) {
126 | 32.dp
127 | } else {
128 | 48.dp
129 | }
130 | }
131 | )
132 |
133 | private fun pinConfiguration(settings: Settings): LazyTablePinConfiguration =
134 | lazyTablePinConfiguration(
135 | columns = when {
136 | settings.pinName && settings.pinImage -> 2
137 | settings.pinName -> 1
138 | else -> 0
139 | },
140 | rows = if (settings.showHeader) 1 else 0,
141 | footer = settings.showFooter,
142 | )
143 |
144 | @Suppress("ComplexMethod")
145 | @Composable
146 | private fun Cell(
147 | pokemon: Pokemon,
148 | column: Int,
149 | ) {
150 | val content = when (column) {
151 | 0 -> pokemon.name
152 | 1 -> "" // Second column is reserved for an image
153 | 2 -> pokemon.number
154 | 3 -> pokemon.height.formatToDecimals()
155 | 4 -> pokemon.weight.formatToDecimals()
156 | 5 -> pokemon.stats.health.toString()
157 | 6 -> pokemon.stats.attack.toString()
158 | 7 -> pokemon.stats.defence.toString()
159 | 8 -> pokemon.stats.specialAttack.toString()
160 | 9 -> pokemon.stats.specialDefence.toString()
161 | 10 -> pokemon.stats.speed.toString()
162 | else -> error("Unknown column index: $column")
163 | }
164 |
165 | Box(
166 | contentAlignment = Alignment.Center,
167 | modifier = Modifier
168 | .background(MaterialTheme.colorScheme.surface)
169 | .border(Dp.Hairline, MaterialTheme.colorScheme.onSurface)
170 | ) {
171 | if (content.isNotEmpty()) {
172 | Text(text = content)
173 | }
174 | if (column == 1) {
175 | AsyncImage(
176 | model = ImageRequest.Builder(LocalPlatformContext.current)
177 | .data(pokemon.imageUrl)
178 | .crossfade(true)
179 | .build(),
180 | contentDescription = null,
181 | )
182 | }
183 | }
184 | }
185 |
186 | @Composable
187 | private fun HeaderCell(column: Int) {
188 | val content = when (column) {
189 | 0 -> "Name"
190 | 1 -> "Img"
191 | 2 -> "Number"
192 | 3 -> "Height (cm)"
193 | 4 -> "Weight (kg)"
194 | 5 -> "Health"
195 | 6 -> "Attack"
196 | 7 -> "Defence"
197 | 8 -> "Sp. attack"
198 | 9 -> "Sp. defence"
199 | 10 -> "Speed"
200 | else -> error("")
201 | }
202 |
203 | Box(
204 | contentAlignment = Alignment.Center,
205 | modifier = Modifier
206 | .background(MaterialTheme.colorScheme.primary)
207 | .border(Dp.Hairline, MaterialTheme.colorScheme.onPrimary)
208 | ) {
209 | Text(text = content)
210 | }
211 | }
212 |
213 | @Composable
214 | private fun Settings(
215 | settings: Settings,
216 | onChange: (Settings) -> Unit,
217 | ) {
218 | @Composable
219 | fun SettingsRow(
220 | text: String,
221 | value: Boolean,
222 | onChange: (Boolean) -> Unit,
223 | ) {
224 | Row(
225 | verticalAlignment = Alignment.CenterVertically,
226 | horizontalArrangement = Arrangement.spacedBy(SpacingMedium),
227 | modifier = Modifier
228 | .fillMaxWidth()
229 | .padding(horizontal = SpacingMedium),
230 | ) {
231 | Text(
232 | text = text,
233 | style = MaterialTheme.typography.titleMedium,
234 | modifier = Modifier.weight(1f)
235 | )
236 | Switch(
237 | checked = value,
238 | onCheckedChange = onChange
239 | )
240 | }
241 | }
242 |
243 | SettingsRow(
244 | text = "Show header",
245 | value = settings.showHeader,
246 | onChange = { onChange(settings.copy(showHeader = it)) }
247 | )
248 |
249 | SettingsRow(
250 | text = "Show footer",
251 | value = settings.showFooter,
252 | onChange = { onChange(settings.copy(showFooter = it)) }
253 | )
254 |
255 | SettingsRow(
256 | text = "Pin name",
257 | value = settings.pinName,
258 | onChange = {
259 | val new = settings.copy(pinName = it, pinImage = if (!it) false else settings.pinImage)
260 | onChange(new)
261 | }
262 | )
263 |
264 | SettingsRow(
265 | text = "Pin image",
266 | value = settings.pinImage,
267 | onChange = {
268 | val new = settings.copy(pinName = if (it) true else settings.pinName, pinImage = it)
269 | onChange(new)
270 | }
271 | )
272 | }
273 |
274 | private data class Settings(
275 | val showHeader: Boolean = true,
276 | val showFooter: Boolean = false,
277 | val pinName: Boolean = false,
278 | val pinImage: Boolean = false,
279 | )
280 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Oleksandr Balan
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/iosdemo/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
11 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
12 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
13 | 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
14 | CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
19 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
20 | 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; };
21 | 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
22 | 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
23 | 7555FF7B242A565900829871 /* LazyTable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyTable.app; sourceTree = BUILT_PRODUCTS_DIR; };
24 | 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
25 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
26 | AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
27 | FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | F85CB1118929364A9C6EFABC /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */,
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 058557D7273AAEEB004C7B11 /* Preview Content */ = {
43 | isa = PBXGroup;
44 | children = (
45 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
46 | );
47 | path = "Preview Content";
48 | sourceTree = "";
49 | };
50 | 42799AB246E5F90AF97AA0EF /* Frameworks */ = {
51 | isa = PBXGroup;
52 | children = (
53 | 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */,
54 | );
55 | name = Frameworks;
56 | sourceTree = "";
57 | };
58 | 7555FF72242A565900829871 = {
59 | isa = PBXGroup;
60 | children = (
61 | AB1DB47929225F7C00F7AF9C /* Configuration */,
62 | 7555FF7D242A565900829871 /* iosApp */,
63 | 7555FF7C242A565900829871 /* Products */,
64 | FEFF387C0A8D172AA4D59CAE /* Pods */,
65 | 42799AB246E5F90AF97AA0EF /* Frameworks */,
66 | );
67 | sourceTree = "";
68 | };
69 | 7555FF7C242A565900829871 /* Products */ = {
70 | isa = PBXGroup;
71 | children = (
72 | 7555FF7B242A565900829871 /* LazyTable.app */,
73 | );
74 | name = Products;
75 | sourceTree = "";
76 | };
77 | 7555FF7D242A565900829871 /* iosApp */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 058557BA273AAA24004C7B11 /* Assets.xcassets */,
81 | 7555FF82242A565900829871 /* ContentView.swift */,
82 | 7555FF8C242A565B00829871 /* Info.plist */,
83 | 2152FB032600AC8F00CF470E /* iOSApp.swift */,
84 | 058557D7273AAEEB004C7B11 /* Preview Content */,
85 | );
86 | path = iosApp;
87 | sourceTree = "";
88 | };
89 | AB1DB47929225F7C00F7AF9C /* Configuration */ = {
90 | isa = PBXGroup;
91 | children = (
92 | AB3632DC29227652001CCB65 /* Config.xcconfig */,
93 | );
94 | path = Configuration;
95 | sourceTree = "";
96 | };
97 | FEFF387C0A8D172AA4D59CAE /* Pods */ = {
98 | isa = PBXGroup;
99 | children = (
100 | 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */,
101 | FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */,
102 | );
103 | path = Pods;
104 | sourceTree = "";
105 | };
106 | /* End PBXGroup section */
107 |
108 | /* Begin PBXNativeTarget section */
109 | 7555FF7A242A565900829871 /* iosApp */ = {
110 | isa = PBXNativeTarget;
111 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
112 | buildPhases = (
113 | 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */,
114 | 7555FF77242A565900829871 /* Sources */,
115 | 7555FF79242A565900829871 /* Resources */,
116 | F85CB1118929364A9C6EFABC /* Frameworks */,
117 | 7B3918CFCD82BB97D674E838 /* [CP] Copy Pods Resources */,
118 | );
119 | buildRules = (
120 | );
121 | dependencies = (
122 | );
123 | name = iosApp;
124 | productName = iosApp;
125 | productReference = 7555FF7B242A565900829871 /* LazyTable.app */;
126 | productType = "com.apple.product-type.application";
127 | };
128 | /* End PBXNativeTarget section */
129 |
130 | /* Begin PBXProject section */
131 | 7555FF73242A565900829871 /* Project object */ = {
132 | isa = PBXProject;
133 | attributes = {
134 | LastSwiftUpdateCheck = 1130;
135 | LastUpgradeCheck = 1130;
136 | ORGANIZATIONNAME = orgName;
137 | TargetAttributes = {
138 | 7555FF7A242A565900829871 = {
139 | CreatedOnToolsVersion = 11.3.1;
140 | };
141 | };
142 | };
143 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
144 | compatibilityVersion = "Xcode 9.3";
145 | developmentRegion = en;
146 | hasScannedForEncodings = 0;
147 | knownRegions = (
148 | en,
149 | Base,
150 | );
151 | mainGroup = 7555FF72242A565900829871;
152 | productRefGroup = 7555FF7C242A565900829871 /* Products */;
153 | projectDirPath = "";
154 | projectRoot = "";
155 | targets = (
156 | 7555FF7A242A565900829871 /* iosApp */,
157 | );
158 | };
159 | /* End PBXProject section */
160 |
161 | /* Begin PBXResourcesBuildPhase section */
162 | 7555FF79242A565900829871 /* Resources */ = {
163 | isa = PBXResourcesBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
167 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | /* End PBXResourcesBuildPhase section */
172 |
173 | /* Begin PBXShellScriptBuildPhase section */
174 | 7B3918CFCD82BB97D674E838 /* [CP] Copy Pods Resources */ = {
175 | isa = PBXShellScriptBuildPhase;
176 | buildActionMask = 2147483647;
177 | files = (
178 | );
179 | inputFileListPaths = (
180 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
181 | );
182 | name = "[CP] Copy Pods Resources";
183 | outputFileListPaths = (
184 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
185 | );
186 | runOnlyForDeploymentPostprocessing = 0;
187 | shellPath = /bin/sh;
188 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
189 | showEnvVarsInLog = 0;
190 | };
191 | 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */ = {
192 | isa = PBXShellScriptBuildPhase;
193 | buildActionMask = 2147483647;
194 | files = (
195 | );
196 | inputFileListPaths = (
197 | );
198 | inputPaths = (
199 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
200 | "${PODS_ROOT}/Manifest.lock",
201 | );
202 | name = "[CP] Check Pods Manifest.lock";
203 | outputFileListPaths = (
204 | );
205 | outputPaths = (
206 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
207 | );
208 | runOnlyForDeploymentPostprocessing = 0;
209 | shellPath = /bin/sh;
210 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
211 | showEnvVarsInLog = 0;
212 | };
213 | /* End PBXShellScriptBuildPhase section */
214 |
215 | /* Begin PBXSourcesBuildPhase section */
216 | 7555FF77242A565900829871 /* Sources */ = {
217 | isa = PBXSourcesBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
221 | 7555FF83242A565900829871 /* ContentView.swift in Sources */,
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | };
225 | /* End PBXSourcesBuildPhase section */
226 |
227 | /* Begin XCBuildConfiguration section */
228 | 7555FFA3242A565B00829871 /* Debug */ = {
229 | isa = XCBuildConfiguration;
230 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
231 | buildSettings = {
232 | ALWAYS_SEARCH_USER_PATHS = NO;
233 | CLANG_ANALYZER_NONNULL = YES;
234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
236 | CLANG_CXX_LIBRARY = "libc++";
237 | CLANG_ENABLE_MODULES = YES;
238 | CLANG_ENABLE_OBJC_ARC = YES;
239 | CLANG_ENABLE_OBJC_WEAK = YES;
240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
241 | CLANG_WARN_BOOL_CONVERSION = YES;
242 | CLANG_WARN_COMMA = YES;
243 | CLANG_WARN_CONSTANT_CONVERSION = YES;
244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
247 | CLANG_WARN_EMPTY_BODY = YES;
248 | CLANG_WARN_ENUM_CONVERSION = YES;
249 | CLANG_WARN_INFINITE_RECURSION = YES;
250 | CLANG_WARN_INT_CONVERSION = YES;
251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
255 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
257 | CLANG_WARN_STRICT_PROTOTYPES = YES;
258 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | COPY_PHASE_STRIP = NO;
263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
264 | ENABLE_STRICT_OBJC_MSGSEND = YES;
265 | ENABLE_TESTABILITY = YES;
266 | GCC_C_LANGUAGE_STANDARD = gnu11;
267 | GCC_DYNAMIC_NO_PIC = NO;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_OPTIMIZATION_LEVEL = 0;
270 | GCC_PREPROCESSOR_DEFINITIONS = (
271 | "DEBUG=1",
272 | "$(inherited)",
273 | );
274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
276 | GCC_WARN_UNDECLARED_SELECTOR = YES;
277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
278 | GCC_WARN_UNUSED_FUNCTION = YES;
279 | GCC_WARN_UNUSED_VARIABLE = YES;
280 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
281 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
282 | MTL_FAST_MATH = YES;
283 | ONLY_ACTIVE_ARCH = YES;
284 | SDKROOT = iphoneos;
285 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
286 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
287 | };
288 | name = Debug;
289 | };
290 | 7555FFA4242A565B00829871 /* Release */ = {
291 | isa = XCBuildConfiguration;
292 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
293 | buildSettings = {
294 | ALWAYS_SEARCH_USER_PATHS = NO;
295 | CLANG_ANALYZER_NONNULL = YES;
296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
298 | CLANG_CXX_LIBRARY = "libc++";
299 | CLANG_ENABLE_MODULES = YES;
300 | CLANG_ENABLE_OBJC_ARC = YES;
301 | CLANG_ENABLE_OBJC_WEAK = YES;
302 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
303 | CLANG_WARN_BOOL_CONVERSION = YES;
304 | CLANG_WARN_COMMA = YES;
305 | CLANG_WARN_CONSTANT_CONVERSION = YES;
306 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
309 | CLANG_WARN_EMPTY_BODY = YES;
310 | CLANG_WARN_ENUM_CONVERSION = YES;
311 | CLANG_WARN_INFINITE_RECURSION = YES;
312 | CLANG_WARN_INT_CONVERSION = YES;
313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
317 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
319 | CLANG_WARN_STRICT_PROTOTYPES = YES;
320 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
321 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
322 | CLANG_WARN_UNREACHABLE_CODE = YES;
323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
324 | COPY_PHASE_STRIP = NO;
325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
326 | ENABLE_NS_ASSERTIONS = NO;
327 | ENABLE_STRICT_OBJC_MSGSEND = YES;
328 | GCC_C_LANGUAGE_STANDARD = gnu11;
329 | GCC_NO_COMMON_BLOCKS = YES;
330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
332 | GCC_WARN_UNDECLARED_SELECTOR = YES;
333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
334 | GCC_WARN_UNUSED_FUNCTION = YES;
335 | GCC_WARN_UNUSED_VARIABLE = YES;
336 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
337 | MTL_ENABLE_DEBUG_INFO = NO;
338 | MTL_FAST_MATH = YES;
339 | SDKROOT = iphoneos;
340 | SWIFT_COMPILATION_MODE = wholemodule;
341 | SWIFT_OPTIMIZATION_LEVEL = "-O";
342 | VALIDATE_PRODUCT = YES;
343 | };
344 | name = Release;
345 | };
346 | 7555FFA6242A565B00829871 /* Debug */ = {
347 | isa = XCBuildConfiguration;
348 | baseConfigurationReference = 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */;
349 | buildSettings = {
350 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
351 | CODE_SIGN_IDENTITY = "Apple Development";
352 | CODE_SIGN_STYLE = Automatic;
353 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
354 | DEVELOPMENT_TEAM = "${TEAM_ID}";
355 | ENABLE_PREVIEWS = YES;
356 | INFOPLIST_FILE = iosApp/Info.plist;
357 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
358 | LD_RUNPATH_SEARCH_PATHS = (
359 | "$(inherited)",
360 | "@executable_path/Frameworks",
361 | );
362 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
363 | PRODUCT_NAME = "${APP_NAME}";
364 | PROVISIONING_PROFILE_SPECIFIER = "";
365 | SWIFT_VERSION = 5.0;
366 | TARGETED_DEVICE_FAMILY = "1,2";
367 | };
368 | name = Debug;
369 | };
370 | 7555FFA7242A565B00829871 /* Release */ = {
371 | isa = XCBuildConfiguration;
372 | baseConfigurationReference = FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */;
373 | buildSettings = {
374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
375 | CODE_SIGN_IDENTITY = "Apple Development";
376 | CODE_SIGN_STYLE = Automatic;
377 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
378 | DEVELOPMENT_TEAM = "${TEAM_ID}";
379 | ENABLE_PREVIEWS = YES;
380 | INFOPLIST_FILE = iosApp/Info.plist;
381 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
382 | LD_RUNPATH_SEARCH_PATHS = (
383 | "$(inherited)",
384 | "@executable_path/Frameworks",
385 | );
386 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
387 | PRODUCT_NAME = "${APP_NAME}";
388 | PROVISIONING_PROFILE_SPECIFIER = "";
389 | SWIFT_VERSION = 5.0;
390 | TARGETED_DEVICE_FAMILY = "1,2";
391 | };
392 | name = Release;
393 | };
394 | /* End XCBuildConfiguration section */
395 |
396 | /* Begin XCConfigurationList section */
397 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
398 | isa = XCConfigurationList;
399 | buildConfigurations = (
400 | 7555FFA3242A565B00829871 /* Debug */,
401 | 7555FFA4242A565B00829871 /* Release */,
402 | );
403 | defaultConfigurationIsVisible = 0;
404 | defaultConfigurationName = Release;
405 | };
406 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
407 | isa = XCConfigurationList;
408 | buildConfigurations = (
409 | 7555FFA6242A565B00829871 /* Debug */,
410 | 7555FFA7242A565B00829871 /* Release */,
411 | );
412 | defaultConfigurationIsVisible = 0;
413 | defaultConfigurationName = Release;
414 | };
415 | /* End XCConfigurationList section */
416 | };
417 | rootObject = 7555FF73242A565900829871 /* Project object */;
418 | }
419 |
--------------------------------------------------------------------------------
/config/detekt/detekt.yml:
--------------------------------------------------------------------------------
1 | build:
2 | maxIssues: 0
3 | excludeCorrectable: false
4 | weights:
5 | # complexity: 2
6 | # LongParameterList: 1
7 | # style: 1
8 | # comments: 1
9 |
10 | config:
11 | validation: true
12 | warningsAsErrors: false
13 | # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
14 | excludes: ''
15 |
16 | processors:
17 | active: true
18 | exclude:
19 | - 'DetektProgressListener'
20 | # - 'KtFileCountProcessor'
21 | # - 'PackageCountProcessor'
22 | # - 'ClassCountProcessor'
23 | # - 'FunctionCountProcessor'
24 | # - 'PropertyCountProcessor'
25 | # - 'ProjectComplexityProcessor'
26 | # - 'ProjectCognitiveComplexityProcessor'
27 | # - 'ProjectLLOCProcessor'
28 | # - 'ProjectCLOCProcessor'
29 | # - 'ProjectLOCProcessor'
30 | # - 'ProjectSLOCProcessor'
31 | # - 'LicenseHeaderLoaderExtension'
32 |
33 | console-reports:
34 | active: true
35 | exclude:
36 | - 'ProjectStatisticsReport'
37 | - 'ComplexityReport'
38 | - 'NotificationReport'
39 | - 'FindingsReport'
40 | - 'FileBasedFindingsReport'
41 | # - 'LiteFindingsReport'
42 |
43 | output-reports:
44 | active: true
45 | exclude:
46 | # - 'TxtOutputReport'
47 | # - 'XmlOutputReport'
48 | # - 'HtmlOutputReport'
49 |
50 | comments:
51 | active: true
52 | AbsentOrWrongFileLicense:
53 | active: false
54 | licenseTemplateFile: 'license.template'
55 | licenseTemplateIsRegex: false
56 | CommentOverPrivateFunction:
57 | active: false
58 | CommentOverPrivateProperty:
59 | active: false
60 | DeprecatedBlockTag:
61 | active: false
62 | EndOfSentenceFormat:
63 | active: false
64 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
65 | OutdatedDocumentation:
66 | active: false
67 | matchTypeParameters: true
68 | matchDeclarationsOrder: true
69 | allowParamOnConstructorProperties: false
70 | UndocumentedPublicClass:
71 | active: true
72 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
73 | searchInNestedClass: true
74 | searchInInnerClass: true
75 | searchInInnerObject: true
76 | searchInInnerInterface: true
77 | UndocumentedPublicFunction:
78 | active: true
79 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
80 | UndocumentedPublicProperty:
81 | active: true
82 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
83 |
84 | complexity:
85 | active: true
86 | ComplexCondition:
87 | active: false
88 | threshold: 4
89 | ComplexInterface:
90 | active: false
91 | threshold: 10
92 | includeStaticDeclarations: false
93 | includePrivateDeclarations: false
94 | ComplexMethod:
95 | active: true
96 | threshold: 15
97 | ignoreSingleWhenExpression: false
98 | ignoreSimpleWhenEntries: false
99 | ignoreNestingFunctions: false
100 | nestingFunctions:
101 | - 'also'
102 | - 'apply'
103 | - 'forEach'
104 | - 'isNotNull'
105 | - 'ifNull'
106 | - 'let'
107 | - 'run'
108 | - 'use'
109 | - 'with'
110 | LabeledExpression:
111 | active: false
112 | ignoredLabels: []
113 | LargeClass:
114 | active: true
115 | threshold: 600
116 | LongMethod:
117 | active: true
118 | threshold: 60
119 | LongParameterList:
120 | active: true
121 | functionThreshold: 10
122 | constructorThreshold: 10
123 | ignoreDefaultParameters: false
124 | ignoreDataClasses: true
125 | ignoreAnnotated: [ 'Composable' ]
126 | ignoreAnnotatedParameter: []
127 | MethodOverloading:
128 | active: false
129 | threshold: 6
130 | NamedArguments:
131 | active: false
132 | threshold: 3
133 | ignoreArgumentsMatchingNames: false
134 | NestedBlockDepth:
135 | active: true
136 | threshold: 4
137 | ReplaceSafeCallChainWithRun:
138 | active: false
139 | StringLiteralDuplication:
140 | active: false
141 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
142 | threshold: 3
143 | ignoreAnnotation: true
144 | excludeStringsWithLessThan5Characters: true
145 | ignoreStringsRegex: '$^'
146 | TooManyFunctions:
147 | active: true
148 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
149 | thresholdInFiles: 11
150 | thresholdInClasses: 11
151 | thresholdInInterfaces: 11
152 | thresholdInObjects: 11
153 | thresholdInEnums: 11
154 | ignoreDeprecated: false
155 | ignorePrivate: false
156 | ignoreOverridden: false
157 |
158 | coroutines:
159 | active: true
160 | GlobalCoroutineUsage:
161 | active: false
162 | InjectDispatcher:
163 | active: false
164 | dispatcherNames:
165 | - 'IO'
166 | - 'Default'
167 | - 'Unconfined'
168 | RedundantSuspendModifier:
169 | active: false
170 | SleepInsteadOfDelay:
171 | active: false
172 | SuspendFunWithCoroutineScopeReceiver:
173 | active: false
174 | SuspendFunWithFlowReturnType:
175 | active: false
176 |
177 | empty-blocks:
178 | active: true
179 | EmptyCatchBlock:
180 | active: true
181 | allowedExceptionNameRegex: '_|(ignore|expected).*'
182 | EmptyClassBlock:
183 | active: true
184 | EmptyDefaultConstructor:
185 | active: true
186 | EmptyDoWhileBlock:
187 | active: true
188 | EmptyElseBlock:
189 | active: true
190 | EmptyFinallyBlock:
191 | active: true
192 | EmptyForBlock:
193 | active: true
194 | EmptyFunctionBlock:
195 | active: true
196 | ignoreOverridden: false
197 | EmptyIfBlock:
198 | active: true
199 | EmptyInitBlock:
200 | active: true
201 | EmptyKtFile:
202 | active: true
203 | EmptySecondaryConstructor:
204 | active: true
205 | EmptyTryBlock:
206 | active: true
207 | EmptyWhenBlock:
208 | active: true
209 | EmptyWhileBlock:
210 | active: true
211 |
212 | exceptions:
213 | active: true
214 | ExceptionRaisedInUnexpectedLocation:
215 | active: true
216 | methodNames:
217 | - 'equals'
218 | - 'finalize'
219 | - 'hashCode'
220 | - 'toString'
221 | InstanceOfCheckForException:
222 | active: false
223 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
224 | NotImplementedDeclaration:
225 | active: false
226 | ObjectExtendsThrowable:
227 | active: false
228 | PrintStackTrace:
229 | active: true
230 | RethrowCaughtException:
231 | active: true
232 | ReturnFromFinally:
233 | active: true
234 | ignoreLabeled: false
235 | SwallowedException:
236 | active: true
237 | ignoredExceptionTypes:
238 | - 'InterruptedException'
239 | - 'MalformedURLException'
240 | - 'NumberFormatException'
241 | - 'ParseException'
242 | allowedExceptionNameRegex: '_|(ignore|expected).*'
243 | ThrowingExceptionFromFinally:
244 | active: true
245 | ThrowingExceptionInMain:
246 | active: false
247 | ThrowingExceptionsWithoutMessageOrCause:
248 | active: true
249 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
250 | exceptions:
251 | - 'ArrayIndexOutOfBoundsException'
252 | - 'Exception'
253 | - 'IllegalArgumentException'
254 | - 'IllegalMonitorStateException'
255 | - 'IllegalStateException'
256 | - 'IndexOutOfBoundsException'
257 | - 'NullPointerException'
258 | - 'RuntimeException'
259 | - 'Throwable'
260 | ThrowingNewInstanceOfSameException:
261 | active: true
262 | TooGenericExceptionCaught:
263 | active: true
264 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
265 | exceptionNames:
266 | - 'ArrayIndexOutOfBoundsException'
267 | - 'Error'
268 | - 'Exception'
269 | - 'IllegalMonitorStateException'
270 | - 'IndexOutOfBoundsException'
271 | - 'NullPointerException'
272 | - 'RuntimeException'
273 | - 'Throwable'
274 | allowedExceptionNameRegex: '_|(ignore|expected).*'
275 | TooGenericExceptionThrown:
276 | active: true
277 | exceptionNames:
278 | - 'Error'
279 | - 'Exception'
280 | - 'RuntimeException'
281 | - 'Throwable'
282 |
283 | naming:
284 | active: true
285 | BooleanPropertyNaming:
286 | active: false
287 | allowedPattern: '^(is|has|are)'
288 | ignoreOverridden: true
289 | ClassNaming:
290 | active: true
291 | classPattern: '[A-Z][a-zA-Z0-9]*'
292 | ConstructorParameterNaming:
293 | active: true
294 | parameterPattern: '[a-z][A-Za-z0-9]*'
295 | privateParameterPattern: '[a-z][A-Za-z0-9]*'
296 | excludeClassPattern: '$^'
297 | ignoreOverridden: true
298 | EnumNaming:
299 | active: true
300 | enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
301 | ForbiddenClassName:
302 | active: false
303 | forbiddenName: []
304 | FunctionMaxLength:
305 | active: false
306 | maximumFunctionNameLength: 30
307 | FunctionMinLength:
308 | active: false
309 | minimumFunctionNameLength: 3
310 | FunctionNaming:
311 | active: true
312 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
313 | functionPattern: '[a-z][a-zA-Z0-9]*'
314 | excludeClassPattern: '$^'
315 | ignoreOverridden: true
316 | ignoreAnnotated: [ 'Composable' ]
317 | FunctionParameterNaming:
318 | active: true
319 | parameterPattern: '[a-z][A-Za-z0-9]*'
320 | excludeClassPattern: '$^'
321 | ignoreOverridden: true
322 | InvalidPackageDeclaration:
323 | active: false
324 | rootPackage: ''
325 | requireRootInDeclaration: false
326 | LambdaParameterNaming:
327 | active: false
328 | parameterPattern: '[a-z][A-Za-z0-9]*|_'
329 | MatchingDeclarationName:
330 | active: true
331 | mustBeFirst: true
332 | MemberNameEqualsClassName:
333 | active: true
334 | ignoreOverridden: true
335 | NoNameShadowing:
336 | active: false
337 | NonBooleanPropertyPrefixedWithIs:
338 | active: false
339 | ObjectPropertyNaming:
340 | active: true
341 | constantPattern: '[A-Za-z][_A-Za-z0-9]*'
342 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
343 | privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
344 | PackageNaming:
345 | active: true
346 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
347 | TopLevelPropertyNaming:
348 | active: true
349 | constantPattern: '[A-Z][A-Za-z0-9]*'
350 | VariableMaxLength:
351 | active: false
352 | maximumVariableNameLength: 64
353 | VariableMinLength:
354 | active: false
355 | minimumVariableNameLength: 1
356 | VariableNaming:
357 | active: true
358 | variablePattern: '[a-z][A-Za-z0-9]*'
359 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
360 | excludeClassPattern: '$^'
361 | ignoreOverridden: true
362 |
363 | performance:
364 | active: true
365 | ArrayPrimitive:
366 | active: true
367 | ForEachOnRange:
368 | active: true
369 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
370 | SpreadOperator:
371 | active: false
372 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
373 | UnnecessaryTemporaryInstantiation:
374 | active: true
375 |
376 | potential-bugs:
377 | active: true
378 | AvoidReferentialEquality:
379 | active: false
380 | forbiddenTypePatterns:
381 | - 'kotlin.String'
382 | CastToNullableType:
383 | active: false
384 | Deprecation:
385 | active: false
386 | DontDowncastCollectionTypes:
387 | active: false
388 | DoubleMutabilityForCollection:
389 | active: false
390 | mutableTypes:
391 | - 'kotlin.collections.MutableList'
392 | - 'kotlin.collections.MutableMap'
393 | - 'kotlin.collections.MutableSet'
394 | - 'java.util.ArrayList'
395 | - 'java.util.LinkedHashSet'
396 | - 'java.util.HashSet'
397 | - 'java.util.LinkedHashMap'
398 | - 'java.util.HashMap'
399 | DuplicateCaseInWhenExpression:
400 | active: true
401 | ElseCaseInsteadOfExhaustiveWhen:
402 | active: false
403 | EqualsAlwaysReturnsTrueOrFalse:
404 | active: true
405 | EqualsWithHashCodeExist:
406 | active: true
407 | ExitOutsideMain:
408 | active: false
409 | ExplicitGarbageCollectionCall:
410 | active: true
411 | HasPlatformType:
412 | active: false
413 | IgnoredReturnValue:
414 | active: false
415 | restrictToAnnotatedMethods: true
416 | returnValueAnnotations:
417 | - '*.CheckResult'
418 | - '*.CheckReturnValue'
419 | ignoreReturnValueAnnotations:
420 | - '*.CanIgnoreReturnValue'
421 | ignoreFunctionCall: []
422 | ImplicitDefaultLocale:
423 | active: true
424 | ImplicitUnitReturnType:
425 | active: false
426 | allowExplicitReturnType: true
427 | InvalidRange:
428 | active: true
429 | IteratorHasNextCallsNextMethod:
430 | active: true
431 | IteratorNotThrowingNoSuchElementException:
432 | active: true
433 | LateinitUsage:
434 | active: false
435 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
436 | ignoreOnClassesPattern: ''
437 | MapGetWithNotNullAssertionOperator:
438 | active: false
439 | MissingPackageDeclaration:
440 | active: false
441 | excludes: ['**/*.kts']
442 | MissingWhenCase:
443 | active: true
444 | allowElseExpression: true
445 | NullCheckOnMutableProperty:
446 | active: false
447 | NullableToStringCall:
448 | active: false
449 | RedundantElseInWhen:
450 | active: true
451 | UnconditionalJumpStatementInLoop:
452 | active: false
453 | UnnecessaryNotNullOperator:
454 | active: true
455 | UnnecessarySafeCall:
456 | active: true
457 | UnreachableCatchBlock:
458 | active: false
459 | UnreachableCode:
460 | active: true
461 | UnsafeCallOnNullableType:
462 | active: true
463 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
464 | UnsafeCast:
465 | active: true
466 | UnusedUnaryOperator:
467 | active: false
468 | UselessPostfixExpression:
469 | active: false
470 | WrongEqualsTypeParameter:
471 | active: true
472 |
473 | style:
474 | active: true
475 | CanBeNonNullable:
476 | active: false
477 | ClassOrdering:
478 | active: false
479 | CollapsibleIfStatements:
480 | active: false
481 | DataClassContainsFunctions:
482 | active: false
483 | conversionFunctionPrefix: 'to'
484 | DataClassShouldBeImmutable:
485 | active: false
486 | DestructuringDeclarationWithTooManyEntries:
487 | active: false
488 | maxDestructuringEntries: 3
489 | EqualsNullCall:
490 | active: true
491 | EqualsOnSignatureLine:
492 | active: false
493 | ExplicitCollectionElementAccessMethod:
494 | active: false
495 | ExplicitItLambdaParameter:
496 | active: false
497 | ExpressionBodySyntax:
498 | active: false
499 | includeLineWrapping: false
500 | ForbiddenComment:
501 | active: false
502 | values:
503 | - 'FIXME:'
504 | - 'STOPSHIP:'
505 | - 'TODO:'
506 | allowedPatterns: ''
507 | customMessage: ''
508 | ForbiddenImport:
509 | active: false
510 | imports: []
511 | forbiddenPatterns: ''
512 | ForbiddenMethodCall:
513 | active: false
514 | methods:
515 | - 'kotlin.io.print'
516 | - 'kotlin.io.println'
517 | ForbiddenPublicDataClass:
518 | active: true
519 | excludes: ['**']
520 | ignorePackages:
521 | - '*.internal'
522 | - '*.internal.*'
523 | ForbiddenVoid:
524 | active: false
525 | ignoreOverridden: false
526 | ignoreUsageInGenerics: false
527 | FunctionOnlyReturningConstant:
528 | active: true
529 | ignoreOverridableFunction: true
530 | ignoreActualFunction: true
531 | excludedFunctions: ''
532 | LibraryCodeMustSpecifyReturnType:
533 | active: true
534 | excludes: ['**']
535 | LibraryEntitiesShouldNotBePublic:
536 | active: true
537 | excludes: ['**']
538 | LoopWithTooManyJumpStatements:
539 | active: true
540 | maxJumpCount: 1
541 | MagicNumber:
542 | active: false
543 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
544 | ignoreNumbers:
545 | - '-1'
546 | - '0'
547 | - '1'
548 | - '2'
549 | ignoreHashCodeFunction: true
550 | ignorePropertyDeclaration: false
551 | ignoreLocalVariableDeclaration: false
552 | ignoreConstantDeclaration: true
553 | ignoreCompanionObjectPropertyDeclaration: true
554 | ignoreAnnotation: false
555 | ignoreNamedArgument: true
556 | ignoreEnums: false
557 | ignoreRanges: false
558 | ignoreExtensionFunctions: true
559 | MandatoryBracesIfStatements:
560 | active: false
561 | MandatoryBracesLoops:
562 | active: false
563 | MaxLineLength:
564 | active: true
565 | maxLineLength: 120
566 | excludePackageStatements: true
567 | excludeImportStatements: true
568 | excludeCommentStatements: false
569 | MayBeConst:
570 | active: true
571 | ModifierOrder:
572 | active: true
573 | MultilineLambdaItParameter:
574 | active: false
575 | NestedClassesVisibility:
576 | active: true
577 | NewLineAtEndOfFile:
578 | active: true
579 | NoTabs:
580 | active: false
581 | ObjectLiteralToLambda:
582 | active: false
583 | OptionalAbstractKeyword:
584 | active: true
585 | OptionalUnit:
586 | active: false
587 | OptionalWhenBraces:
588 | active: false
589 | PreferToOverPairSyntax:
590 | active: false
591 | ProtectedMemberInFinalClass:
592 | active: true
593 | RedundantExplicitType:
594 | active: false
595 | RedundantHigherOrderMapUsage:
596 | active: false
597 | RedundantVisibilityModifierRule:
598 | active: false
599 | ReturnCount:
600 | active: true
601 | max: 2
602 | excludedFunctions: 'equals'
603 | excludeLabeled: false
604 | excludeReturnFromLambda: true
605 | excludeGuardClauses: false
606 | SafeCast:
607 | active: true
608 | SerialVersionUIDInSerializableClass:
609 | active: true
610 | SpacingBetweenPackageAndImports:
611 | active: false
612 | ThrowsCount:
613 | active: true
614 | max: 2
615 | excludeGuardClauses: false
616 | TrailingWhitespace:
617 | active: false
618 | UnderscoresInNumericLiterals:
619 | active: false
620 | acceptableLength: 4
621 | allowNonStandardGrouping: false
622 | UnnecessaryAbstractClass:
623 | active: true
624 | UnnecessaryAnnotationUseSiteTarget:
625 | active: false
626 | UnnecessaryApply:
627 | active: true
628 | UnnecessaryFilter:
629 | active: false
630 | UnnecessaryInheritance:
631 | active: true
632 | UnnecessaryInnerClass:
633 | active: false
634 | UnnecessaryLet:
635 | active: false
636 | UnnecessaryParentheses:
637 | active: false
638 | UntilInsteadOfRangeTo:
639 | active: false
640 | UnusedImports:
641 | active: false
642 | UnusedPrivateClass:
643 | active: true
644 | UnusedPrivateMember:
645 | active: true
646 | allowedNames: '(_|ignored|expected|serialVersionUID)'
647 | UseAnyOrNoneInsteadOfFind:
648 | active: false
649 | UseArrayLiteralsInAnnotations:
650 | active: false
651 | UseCheckNotNull:
652 | active: false
653 | UseCheckOrError:
654 | active: false
655 | UseDataClass:
656 | active: false
657 | allowVars: false
658 | UseEmptyCounterpart:
659 | active: false
660 | UseIfEmptyOrIfBlank:
661 | active: false
662 | UseIfInsteadOfWhen:
663 | active: false
664 | UseIsNullOrEmpty:
665 | active: false
666 | UseOrEmpty:
667 | active: false
668 | UseRequire:
669 | active: false
670 | UseRequireNotNull:
671 | active: false
672 | UselessCallOnNotNull:
673 | active: true
674 | UtilityClassWithPublicConstructor:
675 | active: true
676 | VarCouldBeVal:
677 | active: true
678 | WildcardImport:
679 | active: true
680 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
681 | excludeImports:
682 | - 'java.util.*'
683 |
--------------------------------------------------------------------------------