├── .github
└── workflows
│ ├── publish.yml
│ └── review.yml
├── .gitignore
├── .idea
└── detekt.xml
├── Dangerfile
├── Gemfile
├── LICENSE
├── README.md
├── build.gradle.kts
├── config
├── detekt
│ ├── baseline.xml
│ └── detekt.yml
└── lint
│ ├── lint-baseline.xml
│ └── lint.xml
├── content
└── screenshots
│ ├── delete_rows.png
│ ├── insert_row.png
│ ├── table_content.png
│ ├── tables_list.png
│ └── update_value.png
├── core
├── build.gradle.kts
└── src
│ ├── androidMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── AndroidEnvironmentProvider.kt
│ ├── commonMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── core
│ │ ├── DatabaseWrapper.kt
│ │ ├── EnvironmentProvider.kt
│ │ ├── data
│ │ ├── Column.kt
│ │ ├── ColumnType.kt
│ │ └── Row.kt
│ │ └── mapper
│ │ ├── ColumnsSqlMapper.kt
│ │ ├── CursorWrapper.kt
│ │ ├── RowsSqlMapper.kt
│ │ ├── SingleStringSqlMapper.kt
│ │ ├── SqlMapper.kt
│ │ └── StringSqlMapper.kt
│ └── iosMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ └── IosEnvironmentProvider.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
├── maven-publishing.gradle.kts
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── room-adapter
├── build.gradle.kts
└── src
│ ├── androidMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── room
│ │ └── RoomEnvironmentProvider.kt
│ ├── commonMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── room
│ │ ├── RoomCursorWrapper.kt
│ │ └── RoomWrapper.kt
│ ├── iosMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── room
│ │ └── RoomEnvironmentProvider.kt
│ └── jvmMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ └── adapter
│ └── room
│ └── RoomEnvironmentProvider.kt
├── runtime
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── DelightSqlViewerActivity.kt
│ │ ├── core
│ │ └── util
│ │ │ ├── LaunchManager.kt
│ │ │ └── ShortcutManager.kt
│ │ └── feature
│ │ └── table
│ │ └── presentation
│ │ └── ScreenCloser.kt
│ ├── commonMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── App.kt
│ │ ├── DelightSqlViewer.kt
│ │ ├── core
│ │ ├── component
│ │ │ ├── Component.kt
│ │ │ ├── DefaultRootComponent.kt
│ │ │ ├── Resumable.kt
│ │ │ ├── RootComponent.kt
│ │ │ └── RootContent.kt
│ │ ├── data
│ │ │ └── Theme.kt
│ │ ├── extension
│ │ │ └── String.kt
│ │ ├── presentation
│ │ │ ├── Alert.kt
│ │ │ ├── CheckboxWithText.kt
│ │ │ └── ErrorBox.kt
│ │ └── util
│ │ │ ├── LaunchManager.kt
│ │ │ └── ShortcutManager.kt
│ │ └── feature
│ │ ├── insert
│ │ └── presentation
│ │ │ ├── DefaultInsertComponent.kt
│ │ │ ├── InsertComponent.kt
│ │ │ ├── InsertContent.kt
│ │ │ ├── InsertState.kt
│ │ │ └── InsertValueType.kt
│ │ ├── structure
│ │ └── presentation
│ │ │ ├── DefaultStructureComponent.kt
│ │ │ ├── StructureComponent.kt
│ │ │ ├── StructureContent.kt
│ │ │ └── StructureState.kt
│ │ ├── table
│ │ └── presentation
│ │ │ ├── DefaultTablesListComponent.kt
│ │ │ ├── ScreenCloser.kt
│ │ │ ├── TablesListComponent.kt
│ │ │ ├── TablesListContent.kt
│ │ │ └── TablesListState.kt
│ │ ├── update
│ │ └── presentation
│ │ │ ├── DefaultUpdateComponent.kt
│ │ │ ├── UpdateComponent.kt
│ │ │ ├── UpdateContent.kt
│ │ │ └── UpdateState.kt
│ │ └── viewer
│ │ └── presentation
│ │ ├── DefaultViewerComponent.kt
│ │ ├── ViewerComponent.kt
│ │ ├── ViewerContent.kt
│ │ └── ViewerState.kt
│ ├── iosMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── DelightSqlViewerSceneDelegate.kt
│ │ ├── ShortcutActionHandler.kt
│ │ ├── core
│ │ └── util
│ │ │ ├── IosSceneController.kt
│ │ │ ├── LaunchManager.kt
│ │ │ └── ShortcutManager.kt
│ │ └── feature
│ │ └── table
│ │ └── presentation
│ │ └── ScreenCloser.kt
│ └── jvmMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ ├── core
│ └── util
│ │ ├── LaunchManager.kt
│ │ └── ShortcutManager.kt
│ └── feature
│ └── table
│ └── presentation
│ └── ScreenCloser.kt
├── sample
├── android
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── ru
│ │ │ └── bartwell
│ │ │ └── delightsqlviewer
│ │ │ └── sample
│ │ │ └── android
│ │ │ └── MainActivity.kt
│ │ └── res
│ │ └── values
│ │ └── styles.xml
├── desktop
│ ├── build.gradle.kts
│ └── src
│ │ └── jvmMain
│ │ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── sample
│ │ └── desktop
│ │ └── Main.kt
├── ios
│ ├── iosSample.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── iosSample.xcscheme
│ ├── iosSample.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── iosSample
│ │ ├── AppDelegate.swift
│ │ ├── AppTheme.swift
│ │ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ ├── Info.plist
│ │ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ │ └── iOSApp.swift
└── shared
│ ├── build.gradle.kts
│ └── src
│ ├── androidMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── sample
│ │ └── shared
│ │ └── database
│ │ ├── room
│ │ └── DatabaseBuilder.kt
│ │ └── sqldelight
│ │ └── DriverFactory.kt
│ ├── commonMain
│ ├── kotlin
│ │ └── ru
│ │ │ └── bartwell
│ │ │ └── delightsqlviewer
│ │ │ └── sample
│ │ │ └── shared
│ │ │ ├── App.kt
│ │ │ ├── AppCustomColorScheme.kt
│ │ │ ├── DatabaseInitializer.kt
│ │ │ └── database
│ │ │ ├── room
│ │ │ ├── Database.kt
│ │ │ ├── DatabaseBuilder.kt
│ │ │ ├── Table1Dao.kt
│ │ │ ├── Table1Entity.kt
│ │ │ ├── Table2Dao.kt
│ │ │ └── Table2Entity.kt
│ │ │ └── sqldelight
│ │ │ └── DriverFactory.kt
│ └── sqldelight
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── sample
│ │ └── shared
│ │ ├── table1.sq
│ │ └── table2.sq
│ ├── iosMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── sample
│ │ └── shared
│ │ └── database
│ │ ├── room
│ │ └── DatabaseBuilder.kt
│ │ └── sqldelight
│ │ └── DriverFactory.kt
│ └── jvmMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ └── sample
│ └── shared
│ └── database
│ ├── room
│ └── DatabaseBuilder.kt
│ └── sqldelight
│ └── DriverFactory.kt
├── settings.gradle.kts
├── settings.properties
├── sqldelight-adapter
├── build.gradle.kts
└── src
│ ├── androidMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── sqldelight
│ │ └── SqlDelightEnvironmentProvider.kt
│ ├── commonMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── sqldelight
│ │ ├── SqlDelightCursorWrapper.kt
│ │ └── SqlDelightWrapper.kt
│ ├── iosMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ └── adapter
│ │ └── sqldelight
│ │ └── SqlDelightEnvironmentProvider.kt
│ └── jvmMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ └── adapter
│ └── sqldelight
│ └── SqlDelightEnvironmentProvider.kt
├── stub
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── AndroidEnvironmentProvider.kt
│ │ └── adapter
│ │ ├── room
│ │ └── RoomEnvironmentProvider.kt
│ │ └── sqldelight
│ │ └── SqlDelightEnvironmentProvider.kt
│ ├── commonMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── DelightSqlViewer.kt
│ │ └── core
│ │ └── EnvironmentProvider.kt
│ ├── iosMain
│ └── kotlin
│ │ └── ru
│ │ └── bartwell
│ │ └── delightsqlviewer
│ │ ├── ShortcutActionHandler.kt
│ │ └── adapter
│ │ ├── room
│ │ └── RoomEnvironmentProvider.kt
│ │ └── sqldelight
│ │ └── SqlDelightEnvironmentProvider.kt
│ └── jvmMain
│ └── kotlin
│ └── ru
│ └── bartwell
│ └── delightsqlviewer
│ └── adapter
│ ├── room
│ └── RoomEnvironmentProvider.kt
│ └── sqldelight
│ └── SqlDelightEnvironmentProvider.kt
└── version.properties
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Library to Maven Central
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*'
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Check out repository
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up JDK 17
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: temurin
20 | java-version: 17
21 |
22 | - name: Extract version from tag
23 | id: get_version
24 | run: |
25 | TAG_NAME="${GITHUB_REF_NAME}"
26 | VERSION="${TAG_NAME#v}"
27 | echo "version=$VERSION" >> $GITHUB_OUTPUT
28 |
29 | - name: Write version to version.properties
30 | run: |
31 | echo "libraryVersionName=${{ steps.get_version.outputs.version }}" > version.properties
32 | cat version.properties
33 |
34 | - name: Publish to Maven Central
35 | run: ./gradlew publish
36 | env:
37 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
38 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
39 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
40 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
41 | SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
42 |
43 | - name: Create GitHub Release
44 | id: create_release
45 | uses: actions/create-release@v1
46 | with:
47 | tag_name: ${{ github.ref_name }}
48 | release_name: ${{ github.ref_name }}
49 | body: ""
50 | draft: false
51 | prerelease: false
52 | env:
53 | GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
54 |
--------------------------------------------------------------------------------
/.github/workflows/review.yml:
--------------------------------------------------------------------------------
1 | name: Review PR with Danger
2 |
3 | on:
4 | pull_request:
5 | branches: ["develop"]
6 | types: [opened, synchronize, closed]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 |
13 | - name: Clone repo
14 | uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 100
17 |
18 | - name: set up JDK 17
19 | uses: actions/setup-java@v3
20 | with:
21 | java-version: '17'
22 | distribution: 'temurin'
23 | cache: gradle
24 |
25 | - name: Set up Ruby 3.0
26 | uses: actions/setup-ruby@v1
27 |
28 | - uses: actions/cache@v4
29 | with:
30 | path: vendor/bundle
31 | key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
32 | restore-keys: |
33 | ${{ runner.os }}-gems-
34 |
35 | - name: Bundle install
36 | run: |
37 | gem install bundler
38 | bundle config path vendor/bundle
39 | bundle install --jobs 4 --retry 3
40 |
41 | - name: Grant execute permission for gradlew
42 | run: chmod +x gradlew
43 |
44 | - name: Assemble debug build
45 | run: ./gradlew clean assembleDebug
46 |
47 | - name: Run Lint
48 | run: ./gradlew lintDebug
49 |
50 | - name: Run Detekt
51 | run: ./gradlew detektCheckAll
52 |
53 | - name: Run Danger
54 | run: |
55 | gem install danger
56 | bundle exec danger --verbose --dangerfile=Dangerfile --danger_id=danger-pr --fail-on-errors=true
57 | env:
58 | DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea/*
4 | !.idea/detekt.xml
5 | .DS_Store
6 | build
7 | captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | xcuserdata
12 | .kotlin
13 | /sample/desktop/sample.db
14 |
--------------------------------------------------------------------------------
/.idea/detekt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Dangerfile:
--------------------------------------------------------------------------------
1 | has_wip_label = github.pr_labels.any? { |label| label.include? "Engineers at work" }
2 | has_wip_title = github.pr_title.include? "[WIP]"
3 | if has_wip_label || has_wip_title
4 | warn("PR is marked as Work in Progress")
5 | end
6 |
7 | warn("Big PR") if git.lines_of_code > 5000
8 |
9 | module_dirs = {}
10 | File.open("settings.gradle.kts", "r") do |file|
11 | file.each_line do |line|
12 | line = line.strip
13 | if line =~ /^project\(":(.*?)"\)\.projectDir\s*=\s*file\("([^"]+)"\)/
14 | module_dirs[$1] = $2
15 | end
16 | end
17 | end
18 |
19 | File.open("settings.gradle.kts", "r") do |file_handle|
20 | file_handle.each_line do |line|
21 | line = line.strip
22 | if line.start_with?("include(")
23 | match = line.match(/include\((.*)\)/)
24 | if match
25 | module_string = match[1].gsub(/["']/, '')
26 | gradleModule = module_string.gsub(":", "")
27 | if module_dirs.has_key?(gradleModule) && module_dirs[gradleModule].include?("sample")
28 | next
29 | end
30 | detektFile = "#{gradleModule}/build/reports/detekt/detekt.xml"
31 | if File.file?(detektFile)
32 | kotlin_detekt.report_file = detektFile
33 | kotlin_detekt.skip_gradle_task = true
34 | kotlin_detekt.severity = "warning"
35 | kotlin_detekt.filtering = true
36 | kotlin_detekt.detekt(inline_mode: true)
37 | else
38 | warn("No Detekt report found in #{detektFile} for module #{gradleModule}")
39 | end
40 | else
41 | warn("Could not parse module name from line: #{line}")
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4 |
5 | gem 'danger'
6 | gem 'danger-android_lint'
7 | gem 'danger-kotlin_detekt'
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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 [yyyy] [name of copyright owner]
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Delight SQL Viewer
2 |
3 | **Delight SQL Viewer** is a Kotlin Multiplatform library for Android, iOS, and Desktop applications. It supports both [SQLDelight](https://github.com/cashapp/sqldelight) and [Room Multiplatform](https://developer.android.com/kotlin/multiplatform/room) databases. With version **2.0.0**, developers and testers can view, edit, add, and delete records directly within the app—making debugging and QA efficient by enabling real-time inspection and modification of your app’s database state.
4 |
5 | ## Screenshots
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## Features
24 |
25 | - **Multiplatform Support:** Runs on Android, iOS, and Desktop.
26 | - **Dual Database Support:** Seamlessly work with both SQLDelight and Room databases.
27 | - **Database Inspection:** View, edit, add, and delete records directly from your app.
28 | - **App Shortcuts (Android and iOS):** Automatically adds a shortcut entry for quick access (configurable).
29 | - **Easy Integration:** Add the necessary dependencies and initialize in your platform-specific code.
30 | - **Configurable for Debug/Release:** For debug builds, include full functionality; for release builds, switch to a lightweight stub to reduce app size.
31 |
32 | ## Table of Contents
33 |
34 | 1. [Installation](#installation)
35 | 2. [Initialization](#initialization)
36 | - [Android](#android)
37 | - [iOS](#ios)
38 | - [Desktop](#desktop)
39 | 3. [Launching the Viewer](#launching-the-viewer)
40 | - [Using a Custom Theme](#using-a-custom-theme)
41 | 4. [Shortcuts](#shortcuts)
42 | - [Android Shortcut](#android-shortcut)
43 | - [iOS Shortcut](#ios-shortcut)
44 | - [Desktop](#desktop-shortcut)
45 | 5. [Excluding the Library in Release Builds](#excluding-the-library-in-release-builds)
46 | - [Using the Stub Library](#using-the-stub-library)
47 | - [Omitting Initialization and Launch](#omitting-initialization-and-launch)
48 | 6. [Contributing](#contributing)
49 | 7. [License](#license)
50 |
51 | ---
52 |
53 | ## Installation
54 |
55 | Delight SQL Viewer is published to Maven Central. Add the dependency to your `shared` (common) module:
56 |
57 | ```kotlin
58 | // In shared/build.gradle.kts
59 |
60 | kotlin {
61 | sourceSets {
62 | val commonMain by getting {
63 | dependencies {
64 | api("ru.bartwell.delightsqlviewer:runtime:2.0.0")
65 | api("ru.bartwell.delightsqlviewer:core:2.0.0")
66 | // Choose the adapter based on your database:
67 | api("ru.bartwell.delightsqlviewer:sqldelight-adapter:2.0.0")
68 | // or
69 | // api("ru.bartwell.delightsqlviewer:room-adapter:2.0.0")
70 | }
71 | }
72 | }
73 | }
74 | ```
75 |
76 | For iOS, export the dependencies in your `framework {}` block to make them available in your iOS framework:
77 |
78 | ```kotlin
79 | // In shared/build.gradle.kts
80 |
81 | framework {
82 | export("ru.bartwell.delightsqlviewer:runtime:2.0.0")
83 | export("ru.bartwell.delightsqlviewer:core:2.0.0")
84 | // Choose the adapter based on your database:
85 | export("ru.bartwell.delightsqlviewer:sqldelight-adapter:2.0.0")
86 | // or
87 | // export("ru.bartwell.delightsqlviewer:room-adapter:2.0.0")
88 | }
89 | ```
90 |
91 | ---
92 |
93 | ## Initialization
94 |
95 | After adding the appropriate dependencies, initialize Delight SQL Viewer in your platform-specific code by providing the corresponding environment provider along with the database driver.
96 |
97 | ### Android
98 |
99 | For **SQLDelight**:
100 |
101 | ```kotlin
102 | DelightSqlViewer.init(object : SqlDelightEnvironmentProvider() {
103 | override fun getDriver() = sqlDelightDriver
104 | override fun getContext() = this@MainActivity
105 | })
106 | ```
107 |
108 | For **Room**:
109 |
110 | ```kotlin
111 | DelightSqlViewer.init(object : RoomEnvironmentProvider() {
112 | override fun getDriver() = roomDatabase
113 | override fun getContext() = this@MainActivity
114 | })
115 | ```
116 |
117 | *Note:* Both providers require an Android `Context` along with the respective database instance.
118 |
119 | ### iOS
120 |
121 | For **SQLDelight**:
122 |
123 | ```swift
124 | let provider = SqlDelightEnvironmentProvider(driver: sqlDriver)
125 | DelightSqlViewer.shared.doInit(provider: provider, isShortcutEnabled: true)
126 | ```
127 |
128 | For **Room**:
129 |
130 | ```swift
131 | let provider = RoomEnvironmentProvider(driver: roomDatabase)
132 | DelightSqlViewer.shared.doInit(provider: provider, isShortcutEnabled: true)
133 | ```
134 |
135 | *Note:* The `isShortcutEnabled` parameter determines whether a shortcut is added to the app icon.
136 |
137 | ### Desktop
138 |
139 | For **SQLDelight**:
140 |
141 | ```kotlin
142 | DelightSqlViewer.init(object : SqlDelightEnvironmentProvider() {
143 | override fun getDriver() = sqlDelightDriver
144 | })
145 | ```
146 |
147 | For **Room**:
148 |
149 | ```kotlin
150 | DelightSqlViewer.init(object : RoomEnvironmentProvider() {
151 | override fun getDriver() = roomDatabase
152 | })
153 | ```
154 |
155 | - There is no built-in shortcut on Desktop. Simply call `DelightSqlViewer.launch()` from your own UI controls.
156 |
157 | ---
158 |
159 | ## Launching the Viewer
160 |
161 | Once initialized, you can launch the viewer with a simple call:
162 |
163 | ### Android / Desktop
164 |
165 | ```kotlin
166 | Button(onClick = { DelightSqlViewer.launch() }) {
167 | Text(text = "Launch viewer")
168 | }
169 | ```
170 |
171 | ### iOS
172 |
173 | ```swift
174 | Button("Launch viewer") {
175 | DelightSqlViewer.shared.launch()
176 | }
177 | ```
178 |
179 | #### Using a Custom Theme
180 |
181 | Starting with version **2.1.0**, you can specify a custom theme when launching the viewer. By default, `DelightSqlViewer.launch()` uses `Theme.Auto`, but you may also call:
182 |
183 | ```kotlin
184 | DelightSqlViewer.launch(theme = Theme.Dark)
185 | // or
186 | DelightSqlViewer.launch(theme = Theme.Light)
187 | // or
188 | DelightSqlViewer.launch(theme = Theme.Custom(myColorScheme))
189 | ```
190 |
191 | Theme.Custom takes a Material 3 ColorScheme, allowing you to tailor the viewer’s UI to match your app's design.
192 |
193 | For **iOS**, pass the chosen theme to the `launch(theme:)` method:
194 |
195 | ```swift
196 | Button("Launch viewer in Dark theme") {
197 | DelightSqlViewer.shared.launch(theme: Theme.Dark())
198 | }
199 | ```
200 |
201 | ---
202 |
203 | ## Shortcuts
204 |
205 | ### Android Shortcut
206 |
207 | By default, Delight SQL Viewer adds a shortcut to your app’s launcher icon (accessible via long-press). To disable it, pass `isShortcutEnabled = false` during initialization:
208 |
209 | ```kotlin
210 | DelightSqlViewer.init(
211 | object : AndroidEnvironmentProvider {
212 | override fun getDriver() = sqlDelightDriver // or roomDatabase for Room
213 | override fun getContext() = this@MainActivity
214 | },
215 | isShortcutEnabled = false
216 | )
217 | ```
218 |
219 | ### iOS Shortcut
220 |
221 | On iOS, the library adds a shortcut on the app icon by default. To handle shortcut actions, configure your `AppDelegate` or `UISceneDelegate` as follows:
222 |
223 | ```swift
224 | class AppDelegate: NSObject, UIApplicationDelegate {
225 | func application(
226 | _ application: UIApplication,
227 | configurationForConnecting connectingSceneSession: UISceneSession,
228 | options: UIScene.ConnectionOptions
229 | ) -> UISceneConfiguration {
230 | return ShortcutActionHandler.shared.getConfiguration(session: connectingSceneSession)
231 | }
232 | }
233 | ```
234 |
235 | ### Desktop Shortcut
236 |
237 | Shortcuts are not supported on Desktop. Use your UI controls to manually trigger the viewer.
238 |
239 | ---
240 |
241 | ## Excluding the Library in Release Builds
242 |
243 | Since the viewer is primarily for debugging and development, you may want to exclude it from release builds.
244 |
245 | ### Using the Stub Library
246 |
247 | For release builds, depend on the stub module instead of the full implementation:
248 |
249 | ```kotlin
250 | val isRelease = /* your logic to determine release vs. debug */
251 |
252 | framework {
253 | if (isRelease) {
254 | // Use the stub library for release builds.
255 | export("ru.bartwell.delightsqlviewer:stub:2.0.0")
256 | } else {
257 | // Use the full implementation for debug builds.
258 | export("ru.bartwell.delightsqlviewer:runtime:2.0.0")
259 | export("ru.bartwell.delightsqlviewer:core:2.0.0")
260 | // Choose the appropriate adapter:
261 | export("ru.bartwell.delightsqlviewer:sqldelight-adapter:2.0.0")
262 | // or
263 | // export("ru.bartwell.delightsqlviewer:room-adapter:2.0.0")
264 | }
265 | }
266 | ```
267 |
268 | And in your dependencies:
269 |
270 | ```kotlin
271 | dependencies {
272 | if (isRelease) {
273 | api("ru.bartwell.delightsqlviewer:stub:2.0.0")
274 | } else {
275 | api("ru.bartwell.delightsqlviewer:runtime:2.0.0")
276 | api("ru.bartwell.delightsqlviewer:core:2.0.0")
277 | // Choose the appropriate adapter:
278 | api("ru.bartwell.delightsqlviewer:sqldelight-adapter:2.0.0")
279 | // or
280 | // api("ru.bartwell.delightsqlviewer:room-adapter:2.0.0")
281 | }
282 | }
283 | ```
284 |
285 | ### Omitting Initialization and Launch
286 |
287 | Alternatively, you can simply avoid calling `DelightSqlViewer.init(...)` and `DelightSqlViewer.launch()` in release builds. However, using the stub dependency is generally preferred to prevent unnecessary code inclusion.
288 |
289 | ---
290 |
291 | ## Contributing
292 |
293 | Contributions are welcome! Please feel free to open an issue or submit a pull request with any improvements or suggestions.
294 |
295 | ---
296 |
297 | ## License
298 |
299 | ```
300 | Copyright 2025 Artem Bazhanov
301 |
302 | Licensed under the Apache License, Version 2.0 (the "License");
303 | you may not use this file except in compliance with the License.
304 | You may obtain a copy of the License at
305 |
306 | http://www.apache.org/licenses/LICENSE-2.0
307 | ```
308 |
309 | Delight SQL Viewer is distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
310 |
311 | ---
312 |
313 | **Happy debugging!** If you have any questions or need further assistance, feel free to open an issue.
314 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import io.gitlab.arturbosch.detekt.Detekt
2 | import java.util.Properties
3 |
4 | plugins {
5 | alias(libs.plugins.androidApplication).apply(false)
6 | alias(libs.plugins.androidLibrary).apply(false)
7 | alias(libs.plugins.kotlinAndroid).apply(false)
8 | alias(libs.plugins.kotlinMultiplatform).apply(false)
9 | alias(libs.plugins.compose.compiler).apply(false)
10 | alias(libs.plugins.jetbrainsCompose).apply(false)
11 | alias(libs.plugins.detekt)
12 | }
13 |
14 | val detektFormatting = libs.detekt.formatting.get()
15 | val detektRulesCompose = libs.detekt.rules.compose.get()
16 |
17 | allprojects {
18 | ext {
19 | fun loadProperties(filePath: String): Properties {
20 | val file = file("$rootDir/$filePath")
21 | require(file.canRead()) { "Cannot read file: ${file.absolutePath}" }
22 | return Properties().apply {
23 | file.inputStream().use { load(it) }
24 | }
25 | }
26 |
27 | val settingsProperties = loadProperties("settings.properties")
28 | val versionProperties = loadProperties("version.properties")
29 | val isRelease = settingsProperties["isRelease"]?.toString()?.toBooleanStrictOrNull()
30 | ?: error("Missing or invalid 'isRelease' in settings.properties")
31 | val libraryVersionName = versionProperties["libraryVersionName"]?.toString()
32 | ?: error("Invalid version name in version.properties")
33 |
34 | extra.apply {
35 | set("isRelease", isRelease)
36 | set("libraryVersionName", libraryVersionName)
37 | }
38 | }
39 |
40 | apply(plugin = "io.gitlab.arturbosch.detekt")
41 |
42 | val projectSource = file(projectDir)
43 | val configFile = files("$rootDir/config/detekt/detekt.yml")
44 | val baselineFile = file("$rootDir/config/detekt/baseline.xml")
45 | val kotlinFiles = "**/*.kt"
46 | val ignoredFiles = listOf("**/resources/**", "**/build/**")
47 |
48 | fun configureDetektTask(taskName: String, autoFix: Boolean) {
49 | tasks.register(taskName, Detekt::class) {
50 | description = "Detekt analysis for all modules"
51 | parallel = false
52 | ignoreFailures = true
53 | autoCorrect = autoFix
54 | buildUponDefaultConfig = true
55 | setSource(projectSource)
56 | baseline.set(baselineFile)
57 | config.setFrom(configFile)
58 | include(kotlinFiles)
59 | exclude(ignoredFiles)
60 | reports {
61 | html.required.set(false)
62 | xml.required.set(true)
63 | txt.required.set(false)
64 | }
65 | }
66 | }
67 |
68 | configureDetektTask("detektCheckAll", project.hasProperty("detektAutoFix"))
69 | configureDetektTask("detektFixAll", true)
70 |
71 | dependencies {
72 | detektPlugins(detektFormatting)
73 | detektPlugins(detektRulesCompose)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/config/detekt/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/config/lint/lint-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/config/lint/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/content/screenshots/delete_rows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/content/screenshots/delete_rows.png
--------------------------------------------------------------------------------
/content/screenshots/insert_row.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/content/screenshots/insert_row.png
--------------------------------------------------------------------------------
/content/screenshots/table_content.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/content/screenshots/table_content.png
--------------------------------------------------------------------------------
/content/screenshots/tables_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/content/screenshots/tables_list.png
--------------------------------------------------------------------------------
/content/screenshots/update_value.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/content/screenshots/update_value.png
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | alias(libs.plugins.kotlinSerialization)
7 | id("maven-publish")
8 | id("signing")
9 | }
10 |
11 | group = "ru.bartwell.delightsqlviewer"
12 | version = extra["libraryVersionName"] as String
13 |
14 | kotlin {
15 | androidTarget {
16 | compilations.all {
17 | compileTaskProvider.configure {
18 | compilerOptions {
19 | jvmTarget.set(JvmTarget.JVM_1_8)
20 | }
21 | }
22 | }
23 | publishLibraryVariants("release")
24 | }
25 |
26 | listOf(
27 | iosX64(),
28 | iosArm64(),
29 | iosSimulatorArm64()
30 | ).forEach {
31 | it.binaries.framework {
32 | baseName = "core"
33 | isStatic = true
34 | }
35 | }
36 |
37 | jvm()
38 |
39 | sourceSets {
40 | commonMain.dependencies {
41 | implementation( libs.kotlinx.serialization.json)
42 | implementation(libs.kotlinx.coroutines.core)
43 | }
44 | }
45 |
46 | explicitApi()
47 | }
48 |
49 | android {
50 | namespace = "ru.bartwell.delightsqlviewer.core"
51 | compileSdk = 34
52 |
53 | defaultConfig {
54 | minSdk = 24
55 | }
56 |
57 | compileOptions {
58 | sourceCompatibility = JavaVersion.VERSION_1_8
59 | targetCompatibility = JavaVersion.VERSION_1_8
60 | }
61 | }
62 |
63 | apply(from = "$rootDir/gradle/maven-publishing.gradle.kts")
64 |
--------------------------------------------------------------------------------
/core/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/AndroidEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import android.content.Context
4 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
5 |
6 | public interface AndroidEnvironmentProvider : EnvironmentProvider {
7 | public fun getContext(): Context
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/DatabaseWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import ru.bartwell.delightsqlviewer.core.data.Column
5 | import ru.bartwell.delightsqlviewer.core.mapper.SqlMapper
6 |
7 | public abstract class DatabaseWrapper {
8 | public abstract fun query(sql: String, mapper: SqlMapper): Flow>
9 | public abstract fun querySingle(sql: String, mapper: SqlMapper): Flow
10 | public abstract fun updateSingle(table: String, id: Long, column: Column, value: String?): Flow
11 | public abstract fun insert(table: String, values: Map): Flow
12 | public abstract fun delete(table: String, ids: List): Flow
13 |
14 | protected fun buildUpdateQuery(table: String, column: Column): String =
15 | "UPDATE $table SET ${column.name} = ? WHERE rowid = ?"
16 |
17 | protected fun buildInsertQuery(table: String, values: Map): String {
18 | return if (values.isEmpty()) {
19 | "INSERT INTO $table DEFAULT VALUES;"
20 | } else {
21 | val columnsPart = values.keys.joinToString(",") { it.name }
22 | val valuesPart = List(values.size) { "?" }.joinToString(",")
23 | "INSERT INTO $table ($columnsPart) VALUES ($valuesPart)"
24 | }
25 | }
26 |
27 | protected fun buildDeleteQuery(table: String, ids: List): String {
28 | val whereClause = "rowid=" + ids.joinToString(" OR rowid=")
29 | return "DELETE FROM $table WHERE $whereClause;"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/EnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core
2 |
3 | public interface EnvironmentProvider {
4 | public fun getDriver(): T
5 | public fun getWrapper(): DatabaseWrapper
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/data/Column.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | private const val ROW_ID_COLUMN_NAME = "rowid"
6 |
7 | @Serializable
8 | public data class Column(
9 | val name: String,
10 | val type: ColumnType,
11 | val isNotNullable: Boolean,
12 | val defaultValue: String?,
13 | ) {
14 | val isRowId: Boolean
15 | get() = name == ROW_ID_COLUMN_NAME
16 |
17 | public companion object {
18 | public val ROW_ID_COLUMN: Column = Column(
19 | name = ROW_ID_COLUMN_NAME,
20 | type = ColumnType.INTEGER,
21 | isNotNullable = true,
22 | defaultValue = "",
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/data/ColumnType.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.data
2 |
3 | public enum class ColumnType {
4 | INTEGER, TEXT, REAL, BLOB,
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/data/Row.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.data
2 |
3 | public data class Row(
4 | val id: Long,
5 | val data: List,
6 | )
7 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/ColumnsSqlMapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
5 |
6 | public class ColumnsSqlMapper : SqlMapper {
7 | @Suppress("MagicNumber")
8 | override fun map(cursor: CursorWrapper<*>): Column? {
9 | val name = cursor.getString(1)
10 | val type = cursor.getString(2)
11 | return if (name != null && type != null) {
12 | Column(
13 | name = name,
14 | type = ColumnType.valueOf(type),
15 | isNotNullable = cursor.getBoolean(3) ?: false,
16 | defaultValue = cursor.getString(4),
17 | )
18 | } else {
19 | null
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/CursorWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
5 |
6 | public interface CursorWrapper {
7 | public val value: T
8 | public fun getString(index: Int): String?
9 | public fun getBoolean(index: Int): Boolean?
10 | public fun getLong(index: Int): Long?
11 | public fun getDouble(index: Int): Double?
12 | public fun getBytes(index: Int): ByteArray?
13 |
14 | @OptIn(ExperimentalStdlibApi::class)
15 | public fun getStringOrNull(column: Column, index: Int): String? = when (column.type) {
16 | ColumnType.INTEGER -> getLong(index)?.toString()
17 | ColumnType.TEXT -> getString(index)
18 | ColumnType.REAL -> getDouble(index)?.toString()
19 | ColumnType.BLOB -> getBytes(index)?.toHexString()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/RowsSqlMapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 | import ru.bartwell.delightsqlviewer.core.data.Row
5 |
6 | public class RowsSqlMapper(private val columns: List) : SqlMapper {
7 | public override fun map(cursor: CursorWrapper<*>): Row? {
8 | var id: Long? = null
9 | val data = mutableListOf()
10 | for (column in columns.withIndex()) {
11 | if (column.value.isRowId) {
12 | id = cursor.getLong(column.index)
13 | } else {
14 | data.add(cursor.getStringOrNull(column.value, column.index))
15 | }
16 | }
17 | return id?.let { Row(it, data) }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/SingleStringSqlMapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 |
5 | public class SingleStringSqlMapper(private val column: Column) : SqlMapper {
6 | override fun map(cursor: CursorWrapper<*>): String? {
7 | return cursor.getStringOrNull(column, 0)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/SqlMapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | public interface SqlMapper {
4 | public fun map(cursor: CursorWrapper<*>): T?
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/mapper/StringSqlMapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.mapper
2 |
3 | public class StringSqlMapper : SqlMapper {
4 | override fun map(cursor: CursorWrapper<*>): String? {
5 | return cursor.getString(0)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/IosEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | public abstract class IosEnvironmentProvider(protected val driver: T) : EnvironmentProvider {
6 | final override fun getDriver(): T = driver
7 | }
8 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
3 | org.gradle.caching=true
4 | org.gradle.configuration-cache=true
5 | kotlin.native.enableKlibsCrossCompilation=true
6 |
7 | #Kotlin
8 | kotlin.code.style=official
9 |
10 | #Android
11 | android.useAndroidX=true
12 | android.nonTransitiveRClass=true
13 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.5.2"
3 | kotlinx-coroutines = "1.10.1"
4 | kotlinx-serialization-json = "1.7.3"
5 | sqldelight = "2.0.2"
6 | room = "2.7.0-rc01"
7 | room-driver = "2.5.0-rc01"
8 | kotlin = "2.1.21"
9 | ksp = "2.1.21-2.0.1"
10 | compose = "1.8.1"
11 | compose-material3 = "1.3.1"
12 | androidx-activity-compose = "1.10.1"
13 | decompose = "3.1.0"
14 | decompose-essenty = "2.3.0"
15 | detekt = "1.23.7"
16 | detekt-rules-compose = "1.4.0"
17 |
18 | [libraries]
19 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
20 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
21 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
22 | kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
23 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
24 | compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
25 | decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
26 | decompose-extensions-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }
27 | decompose-essenty-lifecycle-coroutines = { module = "com.arkivanov.essenty:lifecycle-coroutines", version.ref = "decompose-essenty" }
28 | sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
29 | sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
30 | sqldelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
31 | room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
32 | room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
33 | room-driver = { module = "androidx.sqlite:sqlite-bundled", version.ref = "room-driver" }
34 | detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
35 | detekt-rules-compose = { module = "ru.kode:detekt-rules-compose", version.ref = "detekt-rules-compose" }
36 |
37 | [plugins]
38 | androidApplication = { id = "com.android.application", version.ref = "agp" }
39 | androidLibrary = { id = "com.android.library", version.ref = "agp" }
40 | kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
41 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
42 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
43 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
44 | jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose" }
45 | kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
46 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
47 | sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
48 | room = { id = "androidx.room", version.ref = "room" }
49 |
--------------------------------------------------------------------------------
/gradle/maven-publishing.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.api.publish.PublishingExtension
2 | import org.gradle.api.publish.maven.MavenPublication
3 | import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
4 | import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
5 | import org.gradle.api.tasks.bundling.Jar
6 | import org.gradle.plugins.signing.Sign
7 | import org.gradle.plugins.signing.SigningExtension
8 |
9 | plugins.withId("maven-publish") {
10 | val javadocJar = tasks.findByName("javadocJar") as? Jar
11 | ?: tasks.create("javadocJar", Jar::class.java) {
12 | archiveClassifier.set("javadoc")
13 | from(file("empty-javadoc"))
14 | }
15 |
16 | extensions.configure("publishing") {
17 | publications.withType(MavenPublication::class.java).configureEach {
18 | artifact(javadocJar)
19 | pom {
20 | name.set("Delight SQL Viewer")
21 | description.set(
22 | "Delight SQL Viewer is a multiplatform library " +
23 | "that integrates database viewing and editing into your application"
24 | )
25 | url.set("https://github.com/bartwell/delight-sql-viewer")
26 | licenses {
27 | license {
28 | name.set("Apache License, Version 2.0")
29 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
30 | }
31 | }
32 | scm {
33 | connection.set("scm:git:git://github.com/bartwell/delight-sql-viewer.git")
34 | developerConnection.set("scm:git:ssh://github.com/bartwell/delight-sql-viewer.git")
35 | url.set("https://github.com/bartwell/delight-sql-viewer")
36 | }
37 | developers {
38 | developer {
39 | id.set("BArtWell")
40 | name.set("Artem Bazhanov")
41 | email.set("web@bartwell.ru")
42 | }
43 | }
44 | }
45 | }
46 |
47 | repositories {
48 | maven {
49 | name = "OSSRH"
50 | url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
51 | credentials {
52 | username = findProperty("ossrhUsername") as String?
53 | ?: System.getenv("OSSRH_USERNAME")
54 | password = findProperty("ossrhPassword") as String?
55 | ?: System.getenv("OSSRH_PASSWORD")
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | plugins.withId("signing") {
63 | extensions.configure("signing") {
64 | useInMemoryPgpKeys(
65 | findProperty("signingKeyId") as String? ?: System.getenv("SIGNING_KEY_ID"),
66 | findProperty("signingSecretKey") as String? ?: System.getenv("SIGNING_SECRET_KEY"),
67 | findProperty("signingPassword") as String? ?: System.getenv("SIGNING_PASSWORD")
68 | )
69 | val publishingExtension = project.extensions.getByType(PublishingExtension::class.java)
70 | sign(*publishingExtension.publications.toTypedArray())
71 | }
72 | }
73 |
74 | tasks.withType().configureEach {
75 | dependsOn(tasks.withType())
76 | }
77 | tasks.withType().configureEach {
78 | dependsOn(tasks.withType())
79 | }
80 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartwell/delight-sql-viewer/2a91736105f59e545b6d3b3cff2d7e4cd543e2da/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Feb 13 16:29:26 MSK 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/room-adapter/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | id("maven-publish")
7 | id("signing")
8 | }
9 |
10 | group = "ru.bartwell.delightsqlviewer"
11 | version = extra["libraryVersionName"] as String
12 |
13 | kotlin {
14 | androidTarget {
15 | compilations.all {
16 | compileTaskProvider.configure {
17 | compilerOptions {
18 | jvmTarget.set(JvmTarget.JVM_1_8)
19 | }
20 | }
21 | }
22 | publishLibraryVariants("release")
23 | }
24 |
25 | listOf(
26 | iosX64(),
27 | iosArm64(),
28 | iosSimulatorArm64()
29 | ).forEach {
30 | it.binaries.framework {
31 | baseName = "room-adapter"
32 | isStatic = true
33 | }
34 | }
35 |
36 | jvm()
37 |
38 | sourceSets {
39 | commonMain.dependencies {
40 | implementation(projects.core)
41 | implementation(libs.room.runtime)
42 | }
43 | }
44 |
45 | explicitApi()
46 | }
47 |
48 | android {
49 | namespace = "ru.bartwell.delightsqlviewer.adapter.room"
50 | compileSdk = 34
51 |
52 | defaultConfig {
53 | minSdk = 24
54 | }
55 |
56 | compileOptions {
57 | sourceCompatibility = JavaVersion.VERSION_1_8
58 | targetCompatibility = JavaVersion.VERSION_1_8
59 | }
60 | }
61 |
62 | apply(from = "$rootDir/gradle/maven-publishing.gradle.kts")
63 |
--------------------------------------------------------------------------------
/room-adapter/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import androidx.room.RoomDatabase
4 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
6 |
7 | public abstract class RoomEnvironmentProvider : AndroidEnvironmentProvider {
8 | final override fun getWrapper(): DatabaseWrapper = RoomWrapper(getDriver())
9 | }
10 |
--------------------------------------------------------------------------------
/room-adapter/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomCursorWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import androidx.sqlite.SQLiteStatement
4 | import ru.bartwell.delightsqlviewer.core.mapper.CursorWrapper
5 |
6 | internal class RoomCursorWrapper(override val value: SQLiteStatement) : CursorWrapper {
7 | override fun getString(index: Int): String? = value.getTextOrNull(index)
8 | override fun getBoolean(index: Int): Boolean? = value.getBooleanOrNull(index)
9 | override fun getLong(index: Int): Long? = value.getLongOrNull(index)
10 | override fun getDouble(index: Int): Double? = value.getDoubleOrNull(index)
11 | override fun getBytes(index: Int): ByteArray? = value.getBlobOrNull(index)
12 | }
13 |
14 | private fun SQLiteStatement.getTextOrNull(index: Int): String? = getNullable(index) { getText(index) }
15 | private fun SQLiteStatement.getBooleanOrNull(index: Int): Boolean? = getNullable(index) { getBoolean(index) }
16 | private fun SQLiteStatement.getLongOrNull(index: Int): Long? = getNullable(index) { getLong(index) }
17 | private fun SQLiteStatement.getDoubleOrNull(index: Int): Double? = getNullable(index) { getDouble(index) }
18 | private fun SQLiteStatement.getBlobOrNull(index: Int): ByteArray? = getNullable(index) { getBlob(index) }
19 |
20 | private fun SQLiteStatement.getNullable(index: Int, block: (Int) -> T): T? {
21 | if (isNull(index)) return null
22 | @Suppress("SwallowedException", "TooGenericExceptionCaught")
23 | return try {
24 | block(index)
25 | } catch (e: NullPointerException) {
26 | null
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/room-adapter/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import androidx.room.RoomDatabase
4 | import androidx.room.useReaderConnection
5 | import androidx.room.useWriterConnection
6 | import androidx.sqlite.SQLiteStatement
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flow
9 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
10 | import ru.bartwell.delightsqlviewer.core.data.Column
11 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
12 | import ru.bartwell.delightsqlviewer.core.mapper.SqlMapper
13 |
14 | public class RoomWrapper(internal val database: RoomDatabase) : DatabaseWrapper() {
15 |
16 | override fun query(sql: String, mapper: SqlMapper): Flow> = flow {
17 | val result = mutableListOf()
18 | database.useReaderConnection { connection ->
19 | connection.usePrepared(sql) { statement ->
20 | while (statement.step()) {
21 | mapper.map(RoomCursorWrapper(statement))?.let {
22 | result.add(it)
23 | }
24 | }
25 | }
26 | }
27 | emit(result)
28 | }
29 |
30 | override fun querySingle(sql: String, mapper: SqlMapper): Flow = flow {
31 | var result: T? = null
32 | database.useReaderConnection { connection ->
33 | connection.usePrepared(sql) { statement ->
34 | statement.step()
35 | result = mapper.map(RoomCursorWrapper(statement))
36 | }
37 | }
38 | emit(result)
39 | }
40 |
41 | override fun updateSingle(table: String, id: Long, column: Column, value: String?): Flow = flow {
42 | val sql = buildUpdateQuery(table, column)
43 | database.useWriterConnection { connection ->
44 | connection.usePrepared(sql) { statement ->
45 | statement.bindValue(index = 1, column = column, value = value)
46 | statement.bindLong(index = 2, value = id)
47 | statement.step()
48 | }
49 | }
50 | emit(Unit)
51 | }
52 |
53 | override fun insert(table: String, values: Map): Flow = flow {
54 | val sql = buildInsertQuery(table, values)
55 | database.useWriterConnection { connection ->
56 | connection.usePrepared(sql) { statement ->
57 | if (values.isNotEmpty()) {
58 | for ((index, entry) in values.entries.withIndex()) {
59 | statement.bindValue(index = index + 1, column = entry.key, value = entry.value)
60 | }
61 | }
62 | statement.step()
63 | }
64 | }
65 | emit(Unit)
66 | }
67 |
68 | override fun delete(table: String, ids: List): Flow = flow {
69 | if (ids.isNotEmpty()) {
70 | val sql = buildDeleteQuery(table, ids)
71 | database.useWriterConnection { connection ->
72 | connection.usePrepared(sql) { statement ->
73 | statement.step()
74 | }
75 | }
76 | }
77 | emit(Unit)
78 | }
79 | }
80 |
81 | private fun SQLiteStatement.bindLong(index: Int, value: Long?) = value?.let {
82 | bindLong(index, it)
83 | } ?: bindNull(index)
84 |
85 | private fun SQLiteStatement.bindText(index: Int, value: String?) = value?.let {
86 | bindText(index, it)
87 | } ?: bindNull(index)
88 |
89 | private fun SQLiteStatement.bindDouble(index: Int, value: Double?) = value?.let {
90 | bindDouble(index, it)
91 | } ?: bindNull(index)
92 |
93 | private fun SQLiteStatement.bindBlob(index: Int, value: ByteArray?) = value?.let {
94 | bindBlob(index, it)
95 | } ?: bindNull(index)
96 |
97 | @OptIn(ExperimentalStdlibApi::class)
98 | private fun SQLiteStatement.bindValue(index: Int, column: Column, value: String?) {
99 | when (column.type) {
100 | ColumnType.INTEGER -> this.bindLong(index, value?.toLong())
101 | ColumnType.TEXT -> this.bindText(index, value)
102 | ColumnType.REAL -> this.bindDouble(index, value?.toDouble())
103 | ColumnType.BLOB -> this.bindBlob(index, value?.hexToByteArray())
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/room-adapter/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import androidx.room.RoomDatabase
4 | import ru.bartwell.delightsqlviewer.IosEnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
6 |
7 | public class RoomEnvironmentProvider(driver: RoomDatabase) : IosEnvironmentProvider(driver) {
8 | override fun getWrapper(): DatabaseWrapper = RoomWrapper(driver)
9 | }
10 |
--------------------------------------------------------------------------------
/room-adapter/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import androidx.room.RoomDatabase
4 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
5 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
6 |
7 | public abstract class RoomEnvironmentProvider : EnvironmentProvider {
8 | final override fun getWrapper(): DatabaseWrapper = RoomWrapper(getDriver())
9 | }
10 |
--------------------------------------------------------------------------------
/runtime/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | alias(libs.plugins.jetbrainsCompose)
7 | alias(libs.plugins.compose.compiler)
8 | alias(libs.plugins.kotlinSerialization)
9 | alias(libs.plugins.sqldelight)
10 | id("maven-publish")
11 | id("signing")
12 | }
13 |
14 | group = "ru.bartwell.delightsqlviewer"
15 | version = extra["libraryVersionName"] as String
16 |
17 | kotlin {
18 | androidTarget {
19 | compilations.all {
20 | compileTaskProvider.configure {
21 | compilerOptions {
22 | jvmTarget.set(JvmTarget.JVM_1_8)
23 | }
24 | }
25 | }
26 | publishLibraryVariants("release")
27 | }
28 |
29 | listOf(
30 | iosX64(),
31 | iosArm64(),
32 | iosSimulatorArm64()
33 | ).forEach {
34 | it.binaries.framework {
35 | baseName = "runtime"
36 | isStatic = true
37 | }
38 | }
39 |
40 | jvm()
41 |
42 | sourceSets {
43 | commonMain.dependencies {
44 | implementation(projects.core)
45 | implementation(compose.runtime)
46 | implementation(compose.foundation)
47 | implementation(compose.material3)
48 | implementation(compose.materialIconsExtended)
49 | implementation(libs.decompose)
50 | implementation(libs.decompose.extensions.compose)
51 | implementation(libs.decompose.essenty.lifecycle.coroutines)
52 | }
53 | commonTest.dependencies {
54 | implementation(libs.kotlin.test)
55 | }
56 | androidMain.dependencies {
57 | implementation(libs.androidx.activity.compose)
58 | implementation(libs.sqldelight.android.driver)
59 | }
60 | appleMain.dependencies {
61 | implementation(libs.sqldelight.native.driver)
62 | }
63 | jvmMain.dependencies {
64 | implementation(compose.desktop.currentOs)
65 | implementation(libs.sqldelight.driver.sqlite)
66 | }
67 | }
68 |
69 | explicitApi()
70 | }
71 |
72 | android {
73 | namespace = "ru.bartwell.delightsqlviewer"
74 | compileSdk = 34
75 |
76 | defaultConfig {
77 | minSdk = 24
78 | }
79 |
80 | compileOptions {
81 | sourceCompatibility = JavaVersion.VERSION_1_8
82 | targetCompatibility = JavaVersion.VERSION_1_8
83 | }
84 |
85 | buildFeatures {
86 | compose = true
87 | }
88 | }
89 |
90 | sqldelight {}
91 |
92 | apply(from = "$rootDir/gradle/maven-publishing.gradle.kts")
93 |
--------------------------------------------------------------------------------
/runtime/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/runtime/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/DelightSqlViewerActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import com.arkivanov.decompose.defaultComponentContext
8 | import ru.bartwell.delightsqlviewer.core.component.DefaultRootComponent
9 |
10 | public class DelightSqlViewerActivity : ComponentActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | enableEdgeToEdge()
14 | val rootComponent = DefaultRootComponent(defaultComponentContext())
15 | setContent {
16 | App(rootComponent)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/runtime/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/core/util/LaunchManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import android.content.Intent
4 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.DelightSqlViewerActivity
6 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
7 |
8 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
9 | internal actual object LaunchManager {
10 | actual fun launch(environmentProvider: EnvironmentProvider<*>) {
11 | val provider = environmentProvider as AndroidEnvironmentProvider
12 | val context = provider.getContext()
13 | val intent = Intent(context, DelightSqlViewerActivity::class.java)
14 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
15 | context.startActivity(intent)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/runtime/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/core/util/ShortcutManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import android.content.Intent
4 | import android.content.pm.ShortcutInfo
5 | import android.content.pm.ShortcutManager
6 | import android.graphics.drawable.Icon
7 | import android.os.Build
8 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
9 | import ru.bartwell.delightsqlviewer.DelightSqlViewerActivity
10 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
11 |
12 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
13 | internal actual object ShortcutManager {
14 | internal actual fun setup(environmentProvider: EnvironmentProvider<*>) {
15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
16 | val provider = environmentProvider as AndroidEnvironmentProvider
17 | val context = provider.getContext()
18 | context.getSystemService(ShortcutManager::class.java)?.let { shortcutManager ->
19 | val intent = Intent(context, DelightSqlViewerActivity::class.java)
20 | intent.setAction(Intent.ACTION_VIEW)
21 | val shortcut = ShortcutInfo.Builder(context, id)
22 | .setShortLabel(title)
23 | .setLongLabel(subtitle)
24 | .setIcon(Icon.createWithResource(context, android.R.drawable.ic_menu_info_details))
25 | .setIntent(intent)
26 | .build()
27 | shortcutManager.dynamicShortcuts = listOf(shortcut)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/runtime/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/ScreenCloser.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import androidx.activity.compose.LocalActivity
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | internal actual fun screenCloser(): () -> Unit {
8 | val activity = LocalActivity.current
9 | return { activity?.finish() }
10 | }
11 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/App.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.imePadding
7 | import androidx.compose.foundation.layout.systemBarsPadding
8 | import androidx.compose.material3.ColorScheme
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Scaffold
11 | import androidx.compose.material3.darkColorScheme
12 | import androidx.compose.material3.lightColorScheme
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import ru.bartwell.delightsqlviewer.core.component.RootComponent
16 | import ru.bartwell.delightsqlviewer.core.component.RootContent
17 | import ru.bartwell.delightsqlviewer.core.data.Theme
18 |
19 | @Composable
20 | internal fun App(rootComponent: RootComponent) {
21 | MaterialTheme(
22 | colorScheme = DelightSqlViewer.theme.toColorScheme(),
23 | ) {
24 | Scaffold(
25 | modifier = Modifier.fillMaxSize()
26 | .background(MaterialTheme.colorScheme.background)
27 | .systemBarsPadding()
28 | .imePadding()
29 | ) {
30 | RootContent(
31 | modifier = Modifier.fillMaxSize(),
32 | component = rootComponent,
33 | )
34 | }
35 | }
36 | }
37 |
38 | @Composable
39 | private fun Theme.toColorScheme(): ColorScheme = when (this) {
40 | Theme.Auto -> if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
41 | Theme.Dark -> darkColorScheme()
42 | Theme.Light -> lightColorScheme()
43 | is Theme.Custom -> scheme
44 | }
45 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/DelightSqlViewer.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
4 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.core.data.Theme
6 | import ru.bartwell.delightsqlviewer.core.util.LaunchManager
7 | import ru.bartwell.delightsqlviewer.core.util.ShortcutManager
8 | import ru.bartwell.delightsqlviewer.core.util.id
9 |
10 | public object DelightSqlViewer {
11 |
12 | private var environmentProvider: EnvironmentProvider<*>? = null
13 | internal var theme: Theme = Theme.Auto
14 |
15 | public fun init(provider: EnvironmentProvider<*>) {
16 | init(provider, true)
17 | }
18 |
19 | public fun init(provider: EnvironmentProvider<*>, isShortcutEnabled: Boolean) {
20 | environmentProvider = provider
21 | if (isShortcutEnabled) {
22 | ShortcutManager.setup(provider)
23 | }
24 | }
25 |
26 | internal fun getDriver(): DatabaseWrapper {
27 | val driver = environmentProvider?.getWrapper()
28 | requireNotNull(driver) { "Driver is null. Did you call DelightSqlViewer.init()?" }
29 | return driver
30 | }
31 |
32 | public fun launch() {
33 | launch(theme = Theme.Auto)
34 | }
35 |
36 | public fun launch(theme: Theme) {
37 | this.theme = theme
38 | environmentProvider?.let { LaunchManager.launch(it) }
39 | }
40 |
41 | public fun getShortcutId(): String = ShortcutManager.id
42 | }
43 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/component/Component.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.component
2 |
3 | internal interface Component
4 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/component/DefaultRootComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.component
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.router.stack.ChildStack
5 | import com.arkivanov.decompose.router.stack.StackNavigation
6 | import com.arkivanov.decompose.router.stack.childStack
7 | import com.arkivanov.decompose.router.stack.pop
8 | import com.arkivanov.decompose.router.stack.pushNew
9 | import com.arkivanov.decompose.value.Value
10 | import kotlinx.serialization.Serializable
11 | import ru.bartwell.delightsqlviewer.core.data.Column
12 | import ru.bartwell.delightsqlviewer.feature.insert.presentation.DefaultInsertComponent
13 | import ru.bartwell.delightsqlviewer.feature.structure.presentation.DefaultStructureComponent
14 | import ru.bartwell.delightsqlviewer.feature.table.presentation.DefaultTablesListComponent
15 | import ru.bartwell.delightsqlviewer.feature.update.presentation.DefaultUpdateComponent
16 | import ru.bartwell.delightsqlviewer.feature.viewer.presentation.DefaultViewerComponent
17 |
18 | internal class DefaultRootComponent(
19 | componentContext: ComponentContext,
20 | ) : RootComponent, ComponentContext by componentContext {
21 |
22 | private val nav = StackNavigation()
23 |
24 | override val stack: Value>> = childStack(
25 | source = nav,
26 | serializer = Config.serializer(),
27 | initialConfiguration = Config.TablesList,
28 | handleBackButton = true,
29 | childFactory = ::child,
30 | )
31 |
32 | init {
33 | stack.subscribe { childStack ->
34 | val component = childStack.active.instance.component
35 | if (component is Resumable) {
36 | component.onResume()
37 | }
38 | }
39 | }
40 |
41 | private fun child(
42 | config: Config,
43 | componentContext: ComponentContext
44 | ): RootComponent.Child<*> = when (config) {
45 | Config.TablesList -> RootComponent.Child.TablesList(
46 | DefaultTablesListComponent(
47 | componentContext = componentContext,
48 | listItemClicked = { table ->
49 | nav.pushNew(Config.Viewer(table))
50 | }
51 | )
52 | )
53 |
54 | is Config.Viewer -> {
55 | RootComponent.Child.Viewer(
56 | DefaultViewerComponent(
57 | componentContext = componentContext,
58 | table = config.table,
59 | onFinished = {
60 | nav.pop()
61 | },
62 | structureClick = { table -> nav.pushNew(Config.Structure(table)) },
63 | insertClick = { table, columns -> nav.pushNew(Config.Insert(table, columns)) },
64 | cellClick = { table, column, rowId ->
65 | nav.pushNew(Config.Update(table, column, rowId))
66 | }
67 | )
68 | )
69 | }
70 |
71 | is Config.Update -> RootComponent.Child.Update(
72 | DefaultUpdateComponent(
73 | componentContext = componentContext,
74 | table = config.table,
75 | column = config.column,
76 | rowId = config.rowId,
77 | onFinished = {
78 | nav.pop()
79 | },
80 | )
81 | )
82 |
83 | is Config.Insert -> RootComponent.Child.Insert(
84 | DefaultInsertComponent(
85 | componentContext = componentContext,
86 | table = config.table,
87 | columns = config.columns,
88 | onFinished = {
89 | nav.pop()
90 | },
91 | )
92 | )
93 |
94 | is Config.Structure -> RootComponent.Child.Structure(
95 | DefaultStructureComponent(
96 | componentContext = componentContext,
97 | table = config.table,
98 | onFinished = {
99 | nav.pop()
100 | },
101 | )
102 | )
103 | }
104 |
105 | @Serializable
106 | private sealed interface Config {
107 | @Serializable
108 | data object TablesList : Config
109 |
110 | @Serializable
111 | data class Viewer(val table: String) : Config
112 |
113 | @Serializable
114 | data class Update(
115 | val table: String,
116 | val column: Column,
117 | val rowId: Long,
118 | ) : Config
119 |
120 | @Serializable
121 | data class Insert(val table: String, val columns: List) : Config
122 |
123 | @Serializable
124 | data class Structure(val table: String) : Config
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/component/Resumable.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.component
2 |
3 | internal interface Resumable {
4 | fun onResume()
5 | }
6 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/component/RootComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.component
2 |
3 | import com.arkivanov.decompose.router.stack.ChildStack
4 | import com.arkivanov.decompose.value.Value
5 | import ru.bartwell.delightsqlviewer.feature.insert.presentation.InsertComponent
6 | import ru.bartwell.delightsqlviewer.feature.structure.presentation.StructureComponent
7 | import ru.bartwell.delightsqlviewer.feature.table.presentation.TablesListComponent
8 | import ru.bartwell.delightsqlviewer.feature.update.presentation.UpdateComponent
9 | import ru.bartwell.delightsqlviewer.feature.viewer.presentation.ViewerComponent
10 |
11 | internal interface RootComponent {
12 | val stack: Value>>
13 |
14 | sealed class Child(val component: T) {
15 | class TablesList(component: TablesListComponent) : Child(component)
16 | class Viewer(component: ViewerComponent) : Child(component)
17 | class Update(component: UpdateComponent) : Child(component)
18 | class Insert(component: InsertComponent) : Child(component)
19 | class Structure(component: StructureComponent) : Child(component)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/component/RootContent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.component
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.arkivanov.decompose.extensions.compose.stack.Children
7 | import com.arkivanov.decompose.extensions.compose.stack.animation.slide
8 | import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
9 | import ru.bartwell.delightsqlviewer.feature.insert.presentation.InsertContent
10 | import ru.bartwell.delightsqlviewer.feature.structure.presentation.StructureContent
11 | import ru.bartwell.delightsqlviewer.feature.table.presentation.TablesListContent
12 | import ru.bartwell.delightsqlviewer.feature.update.presentation.UpdateContent
13 | import ru.bartwell.delightsqlviewer.feature.viewer.presentation.ViewerContent
14 |
15 | @Composable
16 | internal fun RootContent(
17 | component: RootComponent,
18 | modifier: Modifier = Modifier,
19 | ) {
20 | Children(
21 | stack = component.stack,
22 | modifier = modifier,
23 | animation = stackAnimation(slide()),
24 | ) {
25 | when (val child = it.instance) {
26 | is RootComponent.Child.Viewer -> ViewerContent(
27 | component = child.component,
28 | modifier = Modifier.fillMaxSize(),
29 | )
30 |
31 | is RootComponent.Child.TablesList -> TablesListContent(
32 | component = child.component,
33 | modifier = Modifier.fillMaxSize(),
34 | )
35 |
36 | is RootComponent.Child.Update -> UpdateContent(
37 | component = child.component,
38 | modifier = Modifier.fillMaxSize(),
39 | )
40 |
41 | is RootComponent.Child.Insert -> InsertContent(
42 | component = child.component,
43 | modifier = Modifier.fillMaxSize(),
44 | )
45 |
46 | is RootComponent.Child.Structure -> StructureContent(
47 | component = child.component,
48 | modifier = Modifier.fillMaxSize(),
49 | )
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/data/Theme.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.data
2 |
3 | import androidx.compose.material3.ColorScheme
4 |
5 | public sealed class Theme {
6 | public data object Light : Theme()
7 | public data object Dark : Theme()
8 | public data object Auto : Theme()
9 | public class Custom(public val scheme: ColorScheme) : Theme()
10 | }
11 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/extension/String.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.extension
2 |
3 | internal fun String?.orNull() = this ?: "null"
4 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/presentation/Alert.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.presentation
2 |
3 | import androidx.compose.material3.AlertDialog
4 | import androidx.compose.material3.Text
5 | import androidx.compose.material3.TextButton
6 | import androidx.compose.runtime.Composable
7 |
8 | @Composable
9 | internal fun ErrorAlert(
10 | message: String,
11 | onDismiss: () -> Unit,
12 | ) {
13 | AlertDialog(
14 | onDismissRequest = onDismiss,
15 | title = { Text(text = "Error") },
16 | text = { Text(text = message) },
17 | confirmButton = {
18 | TextButton(onClick = onDismiss) {
19 | Text(text = "Ok")
20 | }
21 | },
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/presentation/CheckboxWithText.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.presentation
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.ColumnScope
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Checkbox
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.unit.dp
15 |
16 | @Composable
17 | internal fun ColumnScope.CheckboxWithText(
18 | text: String,
19 | isChecked: Boolean,
20 | modifier: Modifier = Modifier,
21 | onClick: () -> Unit,
22 | ) {
23 | Row(
24 | modifier = modifier
25 | .align(Alignment.End)
26 | .padding(horizontal = 8.dp)
27 | .clip(MaterialTheme.shapes.large)
28 | .clickable(onClick = onClick)
29 | .padding(horizontal = 8.dp, vertical = 8.dp),
30 | verticalAlignment = Alignment.CenterVertically,
31 | ) {
32 | Text(
33 | modifier = Modifier.padding(horizontal = 8.dp),
34 | text = text,
35 | )
36 | Checkbox(
37 | checked = isChecked,
38 | onCheckedChange = null,
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/presentation/ErrorBox.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.presentation
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.BoxScope
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | internal fun ErrorBox(
14 | error: String?,
15 | modifier: Modifier = Modifier,
16 | content: @Composable BoxScope.() -> Unit
17 | ) {
18 | Box(modifier = modifier) {
19 | if (error == null) {
20 | content()
21 | } else {
22 | Text(
23 | modifier = Modifier.align(Alignment.Center)
24 | .padding(16.dp),
25 | text = error,
26 | )
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/util/LaunchManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | internal expect object LaunchManager {
7 | fun launch(environmentProvider: EnvironmentProvider<*>)
8 | }
9 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/util/ShortcutManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | internal expect object ShortcutManager {
7 | internal fun setup(environmentProvider: EnvironmentProvider<*>)
8 | }
9 |
10 | internal val ShortcutManager.id: String
11 | get() = "delight_sql_viewer_shortcut"
12 |
13 | internal val ShortcutManager.title: String
14 | get() = "SQL Viewer"
15 |
16 | internal val ShortcutManager.subtitle: String
17 | get() = "Open SQL Viewer"
18 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/insert/presentation/DefaultInsertComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.insert.presentation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.value.MutableValue
5 | import com.arkivanov.decompose.value.Value
6 | import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
11 | import ru.bartwell.delightsqlviewer.core.data.Column
12 |
13 | internal class DefaultInsertComponent(
14 | componentContext: ComponentContext,
15 | table: String,
16 | columns: List,
17 | private val onFinished: () -> Unit,
18 | ) : InsertComponent, ComponentContext by componentContext {
19 |
20 | private val _model = MutableValue(InsertState(table = table, columns = columns))
21 | override val model: Value = _model
22 |
23 | override fun onBackPressed() = onFinished()
24 |
25 | override fun onValueChange(column: Column, text: String) {
26 | val newMap = model.value.values.toMutableMap()
27 | newMap[column] = text
28 | _model.value = _model.value.copy(values = newMap)
29 | }
30 |
31 | override fun onValueTypeChange(column: Column, type: InsertValueType) {
32 | val newMap = model.value.valueTypes.toMutableMap()
33 | newMap[column] = type
34 | _model.value = _model.value.copy(valueTypes = newMap)
35 | }
36 |
37 | override fun onSaveClick() {
38 | val values = mutableMapOf()
39 | for (column in model.value.columns) {
40 | values[column] = when (model.value.valueTypes[column]) {
41 | InsertValueType.DEFAULT, null -> continue
42 | InsertValueType.NULL -> null
43 | InsertValueType.VALUE -> model.value.values[column].orEmpty()
44 | }
45 | }
46 | DelightSqlViewer.getDriver()
47 | .insert(model.value.table, values)
48 | .onEach {
49 | onBackPressed()
50 | }
51 | .catch { _model.value = _model.value.copy(insertError = it.toString()) }
52 | .launchIn(coroutineScope())
53 | }
54 |
55 | override fun onAlertDismiss() {
56 | _model.value = _model.value.copy(insertError = null)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/insert/presentation/InsertComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.insert.presentation
2 |
3 | import com.arkivanov.decompose.value.Value
4 | import ru.bartwell.delightsqlviewer.core.component.Component
5 | import ru.bartwell.delightsqlviewer.core.data.Column
6 |
7 | internal interface InsertComponent : Component {
8 | val model: Value
9 |
10 | fun onBackPressed()
11 | fun onValueChange(column: Column, text: String)
12 | fun onValueTypeChange(column: Column, type: InsertValueType)
13 | fun onSaveClick()
14 | fun onAlertDismiss()
15 | }
16 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/insert/presentation/InsertContent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.insert.presentation
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.rememberScrollState
13 | import androidx.compose.foundation.text.KeyboardActions
14 | import androidx.compose.foundation.text.KeyboardOptions
15 | import androidx.compose.foundation.verticalScroll
16 | import androidx.compose.material.icons.Icons
17 | import androidx.compose.material.icons.automirrored.outlined.ArrowBack
18 | import androidx.compose.material3.Button
19 | import androidx.compose.material3.ExperimentalMaterial3Api
20 | import androidx.compose.material3.Icon
21 | import androidx.compose.material3.IconButton
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.OutlinedCard
24 | import androidx.compose.material3.OutlinedTextField
25 | import androidx.compose.material3.RadioButton
26 | import androidx.compose.material3.Text
27 | import androidx.compose.material3.TopAppBar
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.getValue
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.text.input.ImeAction
33 | import androidx.compose.ui.text.input.KeyboardType
34 | import androidx.compose.ui.unit.dp
35 | import com.arkivanov.decompose.extensions.compose.subscribeAsState
36 | import ru.bartwell.delightsqlviewer.core.data.Column
37 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
38 | import ru.bartwell.delightsqlviewer.core.presentation.ErrorAlert
39 |
40 | @OptIn(ExperimentalMaterial3Api::class)
41 | @Composable
42 | internal fun InsertContent(
43 | component: InsertComponent,
44 | modifier: Modifier = Modifier,
45 | ) {
46 | val state by component.model.subscribeAsState()
47 |
48 | Column(modifier = modifier.fillMaxSize()) {
49 | TopAppBar(
50 | title = { Text("Insert row") },
51 | navigationIcon = {
52 | IconButton(onClick = component::onBackPressed) {
53 | Icon(
54 | imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
55 | contentDescription = "Back"
56 | )
57 | }
58 | },
59 | )
60 |
61 | val scrollState = rememberScrollState()
62 | Column(
63 | modifier = Modifier.fillMaxWidth()
64 | .verticalScroll(scrollState)
65 | ) {
66 | val lastColumn = state.columns.last()
67 | state.columns.forEach { column ->
68 | Spacer(modifier = Modifier.height(16.dp))
69 | Card(
70 | state = state,
71 | column = column,
72 | isLastColumn = column == lastColumn,
73 | onValueChange = component::onValueChange,
74 | onValueTypeChange = component::onValueTypeChange,
75 | onSaveClick = component::onSaveClick
76 | )
77 | }
78 |
79 | Spacer(modifier = Modifier.height(8.dp))
80 |
81 | Button(
82 | modifier = Modifier
83 | .fillMaxWidth()
84 | .padding(16.dp),
85 | onClick = component::onSaveClick,
86 | ) {
87 | Text("Insert")
88 | }
89 |
90 | Spacer(modifier = Modifier.height(32.dp))
91 | }
92 | }
93 |
94 | state.insertError?.let { error ->
95 | ErrorAlert(error, component::onAlertDismiss)
96 | }
97 | }
98 |
99 | @Composable
100 | private fun Card(
101 | state: InsertState,
102 | column: Column,
103 | isLastColumn: Boolean,
104 | onValueChange: (Column, String) -> Unit,
105 | onValueTypeChange: (Column, InsertValueType) -> Unit,
106 | onSaveClick: () -> Unit,
107 | ) {
108 | val keyboardType: KeyboardType
109 | val isSingleLine: Boolean
110 | when (column.type) {
111 | ColumnType.INTEGER -> {
112 | keyboardType = KeyboardType.Number
113 | isSingleLine = true
114 | }
115 |
116 | ColumnType.REAL -> {
117 | keyboardType = KeyboardType.Decimal
118 | isSingleLine = true
119 | }
120 |
121 | ColumnType.TEXT, ColumnType.BLOB -> {
122 | keyboardType = KeyboardType.Text
123 | isSingleLine = false
124 | }
125 | }
126 |
127 | val imeAction: ImeAction
128 | val keyboardActions: KeyboardActions
129 | if (isLastColumn) {
130 | imeAction = ImeAction.Done
131 | keyboardActions = KeyboardActions(onDone = { onSaveClick() })
132 | } else {
133 | imeAction = if (column.type == ColumnType.TEXT) ImeAction.Default else ImeAction.Next
134 | keyboardActions = KeyboardActions.Default
135 | }
136 | OutlinedCard(
137 | modifier = Modifier.fillMaxWidth()
138 | .padding(horizontal = 16.dp),
139 | ) {
140 | Text(
141 | modifier = Modifier.padding(horizontal = 16.dp)
142 | .padding(top = 16.dp, bottom = 8.dp),
143 | text = column.name + " (" + column.type.name.lowercase() + ")",
144 | style = MaterialTheme.typography.titleMedium,
145 | )
146 | OutlinedTextField(
147 | modifier = Modifier.fillMaxWidth()
148 | .padding(horizontal = 16.dp),
149 | value = getFieldValue(state, column),
150 | onValueChange = { onValueChange(column, it) },
151 | enabled = state.valueTypes[column] == InsertValueType.VALUE,
152 | singleLine = isSingleLine,
153 | keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction),
154 | keyboardActions = keyboardActions,
155 | )
156 |
157 | TypesGroup(
158 | column = column,
159 | selectedItem = state.valueTypes.getOrDefault(column),
160 | onChange = onValueTypeChange,
161 | )
162 | }
163 | }
164 |
165 | private fun getFieldValue(state: InsertState, column: Column): String {
166 | val nullValue = "[NULL]"
167 | return when (state.valueTypes.getOrDefault(column)) {
168 | InsertValueType.DEFAULT -> column.defaultValue ?: nullValue
169 | InsertValueType.NULL -> nullValue
170 | InsertValueType.VALUE -> state.values[column].orEmpty()
171 | }
172 | }
173 |
174 | @Composable
175 | private fun TypesGroup(
176 | column: Column,
177 | selectedItem: InsertValueType,
178 | onChange: (Column, InsertValueType) -> Unit,
179 | ) {
180 | val items = InsertValueType.entries.filter { it != InsertValueType.NULL || !column.isNotNullable }
181 | Row(
182 | modifier = Modifier.fillMaxWidth()
183 | .padding(vertical = 8.dp)
184 | ) {
185 | items.forEach { item ->
186 | Row(
187 | modifier = Modifier
188 | .weight(1f)
189 | .clickable { onChange(column, item) }
190 | .padding(4.dp),
191 | verticalAlignment = Alignment.CenterVertically,
192 | horizontalArrangement = Arrangement.Center,
193 | ) {
194 | RadioButton(selected = selectedItem == item, onClick = null)
195 | Text(
196 | text = item.title,
197 | modifier = Modifier.padding(start = 4.dp),
198 | )
199 | }
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/insert/presentation/InsertState.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.insert.presentation
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 |
5 | internal data class InsertState(
6 | val table: String,
7 | val columns: List,
8 | val values: Map = emptyMap(),
9 | val valueTypes: Map = emptyMap(),
10 | val insertError: String? = null,
11 | )
12 |
13 | internal fun Map.getOrDefault(column: Column) = this[column] ?: InsertValueType.entries[0]
14 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/insert/presentation/InsertValueType.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.insert.presentation
2 |
3 | internal enum class InsertValueType(val title: String) {
4 | DEFAULT("Default"),
5 | NULL("null"),
6 | VALUE("Value"),
7 | }
8 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/structure/presentation/DefaultStructureComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.structure.presentation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.value.MutableValue
5 | import com.arkivanov.decompose.value.Value
6 | import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
11 | import ru.bartwell.delightsqlviewer.core.mapper.ColumnsSqlMapper
12 |
13 | internal class DefaultStructureComponent(
14 | componentContext: ComponentContext,
15 | table: String,
16 | private val onFinished: () -> Unit,
17 | ) : StructureComponent, ComponentContext by componentContext {
18 |
19 | private val _model = MutableValue(StructureState(table = table))
20 | override val model: Value = _model
21 |
22 | init {
23 | DelightSqlViewer.getDriver()
24 | .query("PRAGMA table_info($table);", ColumnsSqlMapper())
25 | .onEach { _model.value = _model.value.copy(columns = it) }
26 | .catch { _model.value = _model.value.copy(error = it.toString()) }
27 | .launchIn(coroutineScope())
28 | }
29 |
30 | override fun onBackPressed() = onFinished()
31 | }
32 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/structure/presentation/StructureComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.structure.presentation
2 |
3 | import com.arkivanov.decompose.value.Value
4 | import ru.bartwell.delightsqlviewer.core.component.Component
5 |
6 | internal interface StructureComponent : Component {
7 | val model: Value
8 |
9 | fun onBackPressed()
10 | }
11 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/structure/presentation/StructureContent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.structure.presentation
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.lazy.items
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.automirrored.outlined.ArrowBack
13 | import androidx.compose.material3.ExperimentalMaterial3Api
14 | import androidx.compose.material3.Icon
15 | import androidx.compose.material3.IconButton
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.OutlinedCard
18 | import androidx.compose.material3.Text
19 | import androidx.compose.material3.TopAppBar
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.text.style.TextOverflow
27 | import androidx.compose.ui.unit.dp
28 | import com.arkivanov.decompose.extensions.compose.subscribeAsState
29 | import ru.bartwell.delightsqlviewer.core.presentation.ErrorBox
30 |
31 | @OptIn(ExperimentalMaterial3Api::class)
32 | @Composable
33 | internal fun StructureContent(
34 | component: StructureComponent,
35 | modifier: Modifier = Modifier,
36 | ) {
37 | val state by component.model.subscribeAsState()
38 | Column(modifier = modifier) {
39 | TopAppBar(
40 | title = { Text(state.table) },
41 | navigationIcon = {
42 | IconButton(onClick = component::onBackPressed) {
43 | Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = "Back")
44 | }
45 | }
46 | )
47 | ErrorBox(
48 | modifier = Modifier.fillMaxSize(),
49 | error = state.error
50 | ) {
51 | LazyColumn(
52 | modifier = Modifier.padding(16.dp),
53 | verticalArrangement = Arrangement.spacedBy(16.dp),
54 | ) {
55 | items(state.columns) { column ->
56 | OutlinedCard(modifier = Modifier.fillMaxWidth()) {
57 | Column(modifier = Modifier.padding(16.dp)) {
58 | Text(
59 | text = "Name: ${column.name}",
60 | style = MaterialTheme.typography.titleMedium
61 | )
62 | Text(
63 | text = "Type: ${column.type}",
64 | style = MaterialTheme.typography.bodyMedium
65 | )
66 | Text(
67 | text = "Not Null: ${if (column.isNotNullable) "Yes" else "No"}",
68 | style = MaterialTheme.typography.bodySmall
69 | )
70 |
71 | if (column.defaultValue == null) {
72 | Text(
73 | text = "Default value: null",
74 | style = MaterialTheme.typography.bodySmall,
75 | )
76 | } else {
77 | var expanded by remember { mutableStateOf(false) }
78 | Text(
79 | text = "Default value: " + column.defaultValue,
80 | style = MaterialTheme.typography.bodySmall,
81 | maxLines = if (expanded) Int.MAX_VALUE else 2,
82 | overflow = TextOverflow.Ellipsis,
83 | modifier = Modifier.clickable { expanded = !expanded }
84 | )
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/structure/presentation/StructureState.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.structure.presentation
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 |
5 | internal data class StructureState(
6 | val table: String,
7 | val columns: List = emptyList(),
8 | val error: String? = null,
9 | )
10 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/DefaultTablesListComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.value.MutableValue
5 | import com.arkivanov.decompose.value.Value
6 | import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
11 | import ru.bartwell.delightsqlviewer.core.mapper.StringSqlMapper
12 |
13 | internal class DefaultTablesListComponent(
14 | componentContext: ComponentContext,
15 | private val listItemClicked: (String) -> Unit,
16 | ) : TablesListComponent, ComponentContext by componentContext {
17 |
18 | private val _model = MutableValue(TablesListState())
19 | override val model: Value = _model
20 |
21 | init {
22 | val sql = "SELECT name FROM sqlite_master WHERE type='table' " +
23 | "AND name NOT IN ('sqlite_sequence', 'sqlite_stat1', " +
24 | "'sqlite_stat4', 'android_metadata', 'room_master_table');"
25 | DelightSqlViewer.getDriver()
26 | .query(sql, StringSqlMapper())
27 | .onEach { _model.value = _model.value.copy(tables = it) }
28 | .catch { _model.value = _model.value.copy(error = it.toString()) }
29 | .launchIn(coroutineScope())
30 | }
31 |
32 | override fun onListItemClicked(table: String) = listItemClicked(table)
33 | }
34 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/ScreenCloser.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal expect fun screenCloser(): () -> Unit
7 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/TablesListComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import com.arkivanov.decompose.value.Value
4 | import ru.bartwell.delightsqlviewer.core.component.Component
5 |
6 | internal interface TablesListComponent : Component {
7 | val model: Value
8 |
9 | fun onListItemClicked(table: String)
10 | }
11 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/TablesListContent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
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.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.lazy.items
11 | import androidx.compose.foundation.lazy.rememberLazyListState
12 | import androidx.compose.material.icons.Icons
13 | import androidx.compose.material.icons.outlined.Close
14 | import androidx.compose.material3.ExperimentalMaterial3Api
15 | import androidx.compose.material3.Icon
16 | import androidx.compose.material3.IconButton
17 | import androidx.compose.material3.Text
18 | import androidx.compose.material3.TopAppBar
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.unit.dp
23 | import com.arkivanov.decompose.extensions.compose.subscribeAsState
24 | import ru.bartwell.delightsqlviewer.core.presentation.ErrorBox
25 |
26 | @OptIn(ExperimentalMaterial3Api::class)
27 | @Composable
28 | internal fun TablesListContent(
29 | component: TablesListComponent,
30 | modifier: Modifier = Modifier,
31 | ) {
32 | val state by component.model.subscribeAsState()
33 | val screenCloser = screenCloser()
34 |
35 | Column(modifier = modifier) {
36 | TopAppBar(
37 | title = { Text("List") },
38 | navigationIcon = {
39 | IconButton(onClick = screenCloser) {
40 | Icon(imageVector = Icons.Outlined.Close, contentDescription = "Cancel")
41 | }
42 | }
43 | )
44 | ErrorBox(modifier = Modifier.fillMaxSize(), error = state.error) {
45 | LazyColumn(
46 | state = rememberLazyListState(),
47 | modifier = Modifier.fillMaxSize(),
48 | ) {
49 | items(state.tables) { table ->
50 | Row(
51 | modifier = Modifier
52 | .fillMaxWidth()
53 | .clickable { component.onListItemClicked(table) }
54 | .padding(16.dp)
55 | ) {
56 | Text(table)
57 | }
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/TablesListState.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | internal data class TablesListState(
4 | val tables: List = emptyList(),
5 | val error: String? = null,
6 | )
7 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/update/presentation/DefaultUpdateComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.update.presentation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.value.MutableValue
5 | import com.arkivanov.decompose.value.Value
6 | import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
11 | import ru.bartwell.delightsqlviewer.core.data.Column
12 | import ru.bartwell.delightsqlviewer.core.mapper.SingleStringSqlMapper
13 |
14 | internal class DefaultUpdateComponent(
15 | componentContext: ComponentContext,
16 | table: String,
17 | column: Column,
18 | rowId: Long,
19 | private val onFinished: () -> Unit,
20 | ) : UpdateComponent, ComponentContext by componentContext {
21 |
22 | private val _model = MutableValue(
23 | UpdateState(
24 | table = table,
25 | column = column,
26 | rowId = rowId,
27 | )
28 | )
29 | override val model: Value = _model
30 |
31 | init {
32 | updateData()
33 | }
34 |
35 | private fun updateData() {
36 | val table = model.value.table
37 | val columnName = model.value.column.name
38 | val rowId = model.value.rowId
39 | val rowIdColumn = Column.ROW_ID_COLUMN.name
40 | DelightSqlViewer.getDriver()
41 | .querySingle(
42 | "SELECT $columnName FROM $table WHERE $rowIdColumn = $rowId;",
43 | SingleStringSqlMapper(model.value.column)
44 | )
45 | .onEach { value ->
46 | if (value == null) {
47 | _model.value = _model.value.copy(isNull = true)
48 | } else {
49 | _model.value = _model.value.copy(value = value)
50 | }
51 | }
52 | .catch { _model.value = _model.value.copy(loadError = it.toString()) }
53 | .launchIn(coroutineScope())
54 | }
55 |
56 | override fun onBackPressed() = onFinished()
57 |
58 | override fun onValueChange(text: String) {
59 | _model.value = _model.value.copy(value = text)
60 | }
61 |
62 | override fun onNullCheckboxClick() {
63 | _model.value = _model.value.copy(isNull = !model.value.isNull)
64 | }
65 |
66 | override fun onSaveClick() {
67 | val newValue = model.value.value.takeIf { !model.value.isNull }
68 | DelightSqlViewer.getDriver()
69 | .updateSingle(model.value.table, model.value.rowId, model.value.column, newValue)
70 | .onEach {
71 | onBackPressed()
72 | }
73 | .catch { _model.value = _model.value.copy(saveError = it.toString()) }
74 | .launchIn(coroutineScope())
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/update/presentation/UpdateComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.update.presentation
2 |
3 | import com.arkivanov.decompose.value.Value
4 | import ru.bartwell.delightsqlviewer.core.component.Component
5 |
6 | internal interface UpdateComponent : Component {
7 | val model: Value
8 |
9 | fun onBackPressed()
10 | fun onValueChange(text: String)
11 | fun onNullCheckboxClick()
12 | fun onSaveClick()
13 | }
14 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/update/presentation/UpdateContent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.update.presentation
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.rememberScrollState
5 | import androidx.compose.foundation.text.KeyboardActions
6 | import androidx.compose.foundation.text.KeyboardOptions
7 | import androidx.compose.foundation.verticalScroll
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.automirrored.outlined.ArrowBack
10 | import androidx.compose.material3.*
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.text.input.ImeAction
15 | import androidx.compose.ui.text.input.KeyboardType
16 | import androidx.compose.ui.unit.dp
17 | import com.arkivanov.decompose.extensions.compose.subscribeAsState
18 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
19 | import ru.bartwell.delightsqlviewer.core.presentation.CheckboxWithText
20 | import ru.bartwell.delightsqlviewer.core.presentation.ErrorBox
21 |
22 | @OptIn(ExperimentalMaterial3Api::class)
23 | @Composable
24 | internal fun UpdateContent(
25 | component: UpdateComponent,
26 | modifier: Modifier = Modifier,
27 | ) {
28 | val state by component.model.subscribeAsState()
29 | Column(modifier = modifier) {
30 | TopAppBar(
31 | title = { Text(state.column.name + " (" + state.column.type.name.lowercase() + ")") },
32 | navigationIcon = {
33 | IconButton(onClick = component::onBackPressed) {
34 | Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = "Back")
35 | }
36 | },
37 | )
38 |
39 | ErrorBox(state.loadError) {
40 | val scrollState = rememberScrollState()
41 | Column(
42 | modifier = Modifier.fillMaxWidth()
43 | .verticalScroll(scrollState),
44 | ) {
45 | ValueTextField(
46 | state = state,
47 | onValueChange = component::onValueChange,
48 | onSaveClick = component::onSaveClick,
49 | )
50 | if (!state.column.isNotNullable) {
51 | CheckboxWithText(
52 | modifier = Modifier.offset(y = (-8).dp),
53 | text = "null",
54 | isChecked = state.isNull,
55 | onClick = component::onNullCheckboxClick,
56 | )
57 | }
58 | Spacer(modifier = Modifier.weight(1f))
59 | Button(
60 | modifier = Modifier.fillMaxWidth()
61 | .padding(horizontal = 16.dp),
62 | onClick = component::onSaveClick,
63 | ) {
64 | Text("Save")
65 | }
66 | Spacer(modifier = Modifier.height(32.dp))
67 | }
68 | }
69 | }
70 | }
71 |
72 | @Composable
73 | private fun ValueTextField(
74 | state: UpdateState,
75 | onValueChange: (String) -> Unit,
76 | onSaveClick: () -> Unit,
77 | ) {
78 | val keyboardOptions: KeyboardOptions
79 | val keyboardActions: KeyboardActions
80 | val singleLine: Boolean
81 | when (state.column.type) {
82 | ColumnType.INTEGER -> {
83 | keyboardOptions = KeyboardOptions(
84 | keyboardType = KeyboardType.Number,
85 | imeAction = ImeAction.Done,
86 | )
87 | keyboardActions = KeyboardActions(onDone = { onSaveClick() })
88 | singleLine = true
89 | }
90 |
91 | ColumnType.REAL -> {
92 | keyboardOptions = KeyboardOptions(
93 | keyboardType = KeyboardType.Decimal,
94 | imeAction = ImeAction.Done,
95 | )
96 | keyboardActions = KeyboardActions(onDone = { onSaveClick() })
97 | singleLine = true
98 | }
99 |
100 | ColumnType.TEXT, ColumnType.BLOB -> {
101 | keyboardOptions = KeyboardOptions.Default
102 | keyboardActions = KeyboardActions.Default
103 | singleLine = false
104 | }
105 | }
106 |
107 | OutlinedTextField(
108 | modifier = Modifier.fillMaxWidth()
109 | .padding(horizontal = 16.dp)
110 | .padding(top = 16.dp),
111 | value = state.value.orEmpty(),
112 | onValueChange = onValueChange,
113 | enabled = !state.isNull,
114 | keyboardOptions = keyboardOptions,
115 | keyboardActions = keyboardActions,
116 | singleLine = singleLine,
117 | isError = state.saveError != null,
118 | supportingText = { Text(text = state.saveError.orEmpty()) },
119 | )
120 | }
121 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/update/presentation/UpdateState.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.update.presentation
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 |
5 | internal data class UpdateState(
6 | val table: String,
7 | val column: Column,
8 | val rowId: Long,
9 | val value: String? = "",
10 | val isNull: Boolean = false,
11 | val loadError: String? = null,
12 | val saveError: String? = null,
13 | )
14 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/viewer/presentation/DefaultViewerComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.viewer.presentation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.value.MutableValue
5 | import com.arkivanov.decompose.value.Value
6 | import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
11 | import ru.bartwell.delightsqlviewer.core.component.Resumable
12 | import ru.bartwell.delightsqlviewer.core.data.Column
13 | import ru.bartwell.delightsqlviewer.core.mapper.ColumnsSqlMapper
14 | import ru.bartwell.delightsqlviewer.core.mapper.RowsSqlMapper
15 |
16 | internal class DefaultViewerComponent(
17 | componentContext: ComponentContext,
18 | table: String,
19 | private val onFinished: () -> Unit,
20 | private val cellClick: (table: String, column: Column, rowId: Long) -> Unit,
21 | private val structureClick: (table: String) -> Unit,
22 | private val insertClick: (table: String, columns: List) -> Unit,
23 | ) : ViewerComponent, ComponentContext by componentContext, Resumable {
24 |
25 | private val _model = MutableValue(ViewerState(table = table))
26 | override val model: Value = _model
27 |
28 | override fun onResume() {
29 | updateTable()
30 | }
31 |
32 | private fun updateTable() {
33 | loadColumns()
34 | }
35 |
36 | private fun loadColumns() {
37 | val table = model.value.table
38 | DelightSqlViewer.getDriver()
39 | .query("PRAGMA table_info($table);", ColumnsSqlMapper())
40 | .onEach { columns ->
41 | _model.value = _model.value.copy(columns = columns + listOf(Column.ROW_ID_COLUMN))
42 | loadRows()
43 | }
44 | .catch { _model.value = _model.value.copy(loadError = it.toString()) }
45 | .launchIn(coroutineScope())
46 | }
47 |
48 | private fun loadRows() {
49 | val table = model.value.table
50 | val columns = model.value.columns.joinToString(",") { it.name }
51 | DelightSqlViewer.getDriver()
52 | .query("SELECT $columns FROM $table;", RowsSqlMapper(model.value.columns))
53 | .onEach { _model.value = _model.value.copy(rows = it) }
54 | .catch { _model.value = _model.value.copy(loadError = it.toString()) }
55 | .launchIn(coroutineScope())
56 | }
57 |
58 | override fun onDeleteClick() {
59 | _model.value = model.value.copy(
60 | isDeleteMode = !model.value.isDeleteMode,
61 | selectedRows = if (model.value.isDeleteMode) emptyList() else model.value.selectedRows,
62 | )
63 | }
64 |
65 | override fun onRowSelected(rowId: Long, isSelected: Boolean) {
66 | val newSelected = if (isSelected) {
67 | model.value.selectedRows + rowId
68 | } else {
69 | model.value.selectedRows - rowId
70 | }
71 | _model.value = model.value.copy(selectedRows = newSelected)
72 | }
73 |
74 | override fun onBackPressed() = onFinished()
75 |
76 | override fun onCancelDeleteClick() {
77 | _model.value = model.value.copy(isDeleteMode = false)
78 | }
79 |
80 | override fun onConfirmDeleteClick() {
81 | DelightSqlViewer.getDriver()
82 | .delete(model.value.table, model.value.selectedRows)
83 | .onEach {
84 | updateTable()
85 | _model.value = model.value.copy(isDeleteMode = false)
86 | }
87 | .catch { _model.value = _model.value.copy(deleteError = it.toString()) }
88 | .launchIn(coroutineScope())
89 | }
90 |
91 | override fun onStructureClick() = structureClick(model.value.table)
92 |
93 | override fun onInsertClick() = insertClick(model.value.table, model.value.visibleColumns)
94 |
95 | override fun onCellClick(column: Column, rowId: Long) = cellClick(model.value.table, column, rowId)
96 |
97 | override fun onAlertDismiss() {
98 | _model.value = model.value.copy(deleteError = null)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/viewer/presentation/ViewerComponent.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.viewer.presentation
2 |
3 | import com.arkivanov.decompose.value.Value
4 | import ru.bartwell.delightsqlviewer.core.component.Component
5 | import ru.bartwell.delightsqlviewer.core.data.Column
6 |
7 | internal interface ViewerComponent : Component {
8 | val model: Value
9 |
10 | fun onBackPressed()
11 | fun onStructureClick()
12 | fun onInsertClick()
13 | fun onCellClick(column: Column, rowId: Long)
14 | fun onDeleteClick()
15 | fun onRowSelected(rowId: Long, isSelected: Boolean)
16 | fun onCancelDeleteClick()
17 | fun onConfirmDeleteClick()
18 | fun onAlertDismiss()
19 | }
20 |
--------------------------------------------------------------------------------
/runtime/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/feature/viewer/presentation/ViewerState.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.viewer.presentation
2 |
3 | import ru.bartwell.delightsqlviewer.core.data.Column
4 | import ru.bartwell.delightsqlviewer.core.data.Row
5 |
6 | internal data class ViewerState(
7 | val table: String,
8 | val columns: List = emptyList(),
9 | val rows: List = emptyList(),
10 | val isDeleteMode: Boolean = false,
11 | val selectedRows: List = emptyList(),
12 | val deleteError: String? = null,
13 | val loadError: String? = null,
14 | ) {
15 | val visibleColumns: List
16 | get() = columns.filter { !it.isRowId }
17 | }
18 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/DelightSqlViewerSceneDelegate.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import kotlinx.cinterop.BetaInteropApi
4 | import platform.UIKit.UIApplicationShortcutItem
5 | import platform.UIKit.UIScene
6 | import platform.UIKit.UISceneConnectionOptions
7 | import platform.UIKit.UISceneSession
8 | import platform.UIKit.UIWindowScene
9 | import platform.UIKit.UIWindowSceneDelegateProtocol
10 | import platform.darwin.NSObject
11 | import ru.bartwell.delightsqlviewer.core.util.ShortcutManager
12 | import ru.bartwell.delightsqlviewer.core.util.id
13 |
14 | @OptIn(BetaInteropApi::class)
15 | internal class DelightSqlViewerSceneDelegate @OverrideInit constructor() : NSObject(), UIWindowSceneDelegateProtocol {
16 |
17 | override fun scene(
18 | scene: UIScene,
19 | willConnectToSession: UISceneSession,
20 | options: UISceneConnectionOptions
21 | ) {
22 | handleAction(options.shortcutItem)
23 | }
24 |
25 | override fun windowScene(
26 | windowScene: UIWindowScene,
27 | performActionForShortcutItem: UIApplicationShortcutItem,
28 | completionHandler: (Boolean) -> Unit
29 | ) {
30 | val isHandled = handleAction(performActionForShortcutItem)
31 | completionHandler(isHandled)
32 | }
33 |
34 | private fun handleAction(shortcutItem: UIApplicationShortcutItem?): Boolean {
35 | return if (shortcutItem?.type == ShortcutManager.id) {
36 | DelightSqlViewer.launch()
37 | true
38 | } else {
39 | false
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/ShortcutActionHandler.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import kotlinx.cinterop.BetaInteropApi
4 | import platform.UIKit.UISceneConfiguration
5 | import platform.UIKit.UISceneSession
6 |
7 | public object ShortcutActionHandler {
8 |
9 | @OptIn(BetaInteropApi::class)
10 | public fun getConfiguration(session: UISceneSession): UISceneConfiguration {
11 | val configuration = UISceneConfiguration(
12 | name = session.configuration.name,
13 | sessionRole = session.role,
14 | )
15 | configuration.delegateClass = DelightSqlViewerSceneDelegate().`class`()
16 | return configuration
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/core/util/IosSceneController.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import androidx.compose.ui.window.ComposeUIViewController
4 | import com.arkivanov.decompose.DefaultComponentContext
5 | import com.arkivanov.essenty.lifecycle.LifecycleRegistry
6 | import com.arkivanov.essenty.lifecycle.create
7 | import platform.UIKit.UIApplication
8 | import platform.UIKit.UIModalPresentationFullScreen
9 | import platform.UIKit.UINavigationController
10 | import platform.UIKit.UITabBarController
11 | import platform.UIKit.UIViewController
12 | import ru.bartwell.delightsqlviewer.App
13 | import ru.bartwell.delightsqlviewer.core.component.DefaultRootComponent
14 | import kotlin.experimental.ExperimentalNativeApi
15 | import kotlin.native.ref.WeakReference
16 |
17 | @OptIn(ExperimentalNativeApi::class)
18 | internal object IosSceneController {
19 |
20 | private var _viewerViewControllerInstance: WeakReference? = null
21 | private val viewerViewControllerInstance: UIViewController?
22 | get() = _viewerViewControllerInstance?.get()
23 |
24 | fun present() {
25 | if (viewerViewControllerInstance != null) {
26 | return
27 | }
28 | val lifecycle = LifecycleRegistry()
29 | val componentContext = DefaultComponentContext(lifecycle)
30 | val rootComponent = DefaultRootComponent(componentContext)
31 | val uiViewController = ComposeUIViewController(configure = { enforceStrictPlistSanityCheck = false }) {
32 | App(rootComponent)
33 | }
34 | lifecycle.create()
35 | _viewerViewControllerInstance = WeakReference(uiViewController)
36 | uiViewController.modalPresentationStyle = UIModalPresentationFullScreen
37 | getTopMostViewController()?.presentViewController(uiViewController, animated = true, completion = null)
38 | }
39 |
40 | private fun getTopMostViewController(
41 | base: UIViewController? = UIApplication.sharedApplication.keyWindow?.rootViewController
42 | ): UIViewController? {
43 | if (base == null) {
44 | return null
45 | }
46 | return when (base) {
47 | is UINavigationController -> getTopMostViewController(base.visibleViewController)
48 | is UITabBarController -> base.selectedViewController?.let { getTopMostViewController(it) }
49 | else -> if (base.presentedViewController != null) {
50 | getTopMostViewController(base.presentedViewController)
51 | } else {
52 | base
53 | }
54 | }
55 | }
56 |
57 | fun dismiss() {
58 | viewerViewControllerInstance?.dismissViewControllerAnimated(true, completion = null)
59 | _viewerViewControllerInstance = null
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/core/util/LaunchManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | internal actual object LaunchManager {
7 | actual fun launch(environmentProvider: EnvironmentProvider<*>) = IosSceneController.present()
8 | }
9 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/core/util/ShortcutManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import platform.UIKit.UIApplication
4 | import platform.UIKit.UIApplicationShortcutIcon
5 | import platform.UIKit.UIApplicationShortcutIconType
6 | import platform.UIKit.UIApplicationShortcutItem
7 | import platform.UIKit.shortcutItems
8 | import platform.darwin.dispatch_async
9 | import platform.darwin.dispatch_get_main_queue
10 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
11 |
12 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
13 | internal actual object ShortcutManager {
14 | internal actual fun setup(environmentProvider: EnvironmentProvider<*>) {
15 | val shortcutItem = UIApplicationShortcutItem(
16 | type = id,
17 | localizedTitle = title,
18 | localizedSubtitle = subtitle,
19 | icon = UIApplicationShortcutIcon.iconWithType(
20 | UIApplicationShortcutIconType.UIApplicationShortcutIconTypeFavorite
21 | ),
22 | userInfo = null,
23 | )
24 | dispatch_async(dispatch_get_main_queue()) {
25 | UIApplication.sharedApplication.shortcutItems = listOf(shortcutItem)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/runtime/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/ScreenCloser.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import androidx.compose.runtime.Composable
4 | import ru.bartwell.delightsqlviewer.core.util.IosSceneController
5 |
6 | @Composable
7 | internal actual fun screenCloser(): () -> Unit {
8 | return { IosSceneController.dismiss() }
9 | }
10 |
--------------------------------------------------------------------------------
/runtime/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/core/util/LaunchManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import androidx.compose.runtime.CompositionLocalProvider
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import androidx.compose.ui.awt.ComposeWindow
6 | import com.arkivanov.decompose.DefaultComponentContext
7 | import com.arkivanov.essenty.lifecycle.LifecycleRegistry
8 | import ru.bartwell.delightsqlviewer.App
9 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
10 | import ru.bartwell.delightsqlviewer.core.component.DefaultRootComponent
11 | import java.awt.Dimension
12 |
13 | private const val WINDOW_WIDTH = 800
14 | private const val WINDOW_HEIGHT = 600
15 | private const val MIN_WINDOW_SIZE = 400
16 |
17 | internal val LocalComposeWindow = staticCompositionLocalOf { null }
18 |
19 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
20 | internal actual object LaunchManager {
21 | actual fun launch(environmentProvider: EnvironmentProvider<*>) {
22 | // environmentProvider as DesktopEnvironmentProvider
23 | val lifecycle = LifecycleRegistry()
24 | val componentContext = DefaultComponentContext(lifecycle)
25 | val rootComponent = DefaultRootComponent(componentContext)
26 |
27 | val window = ComposeWindow().apply {
28 | title = "Viewer"
29 | size = Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)
30 | minimumSize = Dimension(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)
31 | preferredSize = Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)
32 | setLocationRelativeTo(null)
33 | setContent {
34 | CompositionLocalProvider(LocalComposeWindow provides window) {
35 | App(rootComponent)
36 | }
37 | }
38 | }
39 | window.pack()
40 | window.isVisible = true
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/runtime/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/core/util/ShortcutManager.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core.util
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "EmptyFunctionBlock")
6 | internal actual object ShortcutManager {
7 | internal actual fun setup(environmentProvider: EnvironmentProvider<*>) {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/runtime/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/feature/table/presentation/ScreenCloser.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.feature.table.presentation
2 |
3 | import androidx.compose.runtime.Composable
4 | import ru.bartwell.delightsqlviewer.core.util.LocalComposeWindow
5 |
6 | @Composable
7 | internal actual fun screenCloser(): () -> Unit {
8 | val window = LocalComposeWindow.current
9 | return { window?.dispose() }
10 | }
11 |
--------------------------------------------------------------------------------
/sample/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidApplication)
3 | alias(libs.plugins.kotlinAndroid)
4 | alias(libs.plugins.compose.compiler)
5 | }
6 |
7 | android {
8 | namespace = "ru.bartwell.delightsqlviewer.sample.android"
9 | compileSdk = 35
10 | defaultConfig {
11 | applicationId = "ru.bartwell.delightsqlviewer.sample.android"
12 | minSdk = 24
13 | targetSdk = 35
14 | versionCode = 1
15 | versionName = "1.0"
16 | }
17 | buildFeatures {
18 | compose = true
19 | }
20 | packaging {
21 | resources {
22 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
23 | }
24 | }
25 | buildTypes {
26 | getByName("release") {
27 | isMinifyEnabled = false
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility = JavaVersion.VERSION_1_8
32 | targetCompatibility = JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = "1.8"
36 | }
37 | lint {
38 | lintConfig = file("config/lint/lint.xml")
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation(projects.shared)
44 | implementation(libs.compose.material3)
45 | implementation(libs.androidx.activity.compose)
46 | }
--------------------------------------------------------------------------------
/sample/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/sample/android/src/main/java/ru/bartwell/delightsqlviewer/sample/android/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.android
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
7 | import ru.bartwell.delightsqlviewer.adapter.room.RoomEnvironmentProvider
8 | import ru.bartwell.delightsqlviewer.adapter.sqldelight.SqlDelightEnvironmentProvider
9 | import ru.bartwell.delightsqlviewer.sample.shared.App
10 | import ru.bartwell.delightsqlviewer.sample.shared.DatabaseInitializer
11 | import ru.bartwell.delightsqlviewer.sample.shared.database.room.AppDatabase
12 | import ru.bartwell.delightsqlviewer.sample.shared.database.room.DatabaseBuilder
13 | import ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight.DriverFactory
14 |
15 | class MainActivity : ComponentActivity() {
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | val databaseInitializer = object : DatabaseInitializer {
19 | override fun initSqlDelight() {
20 | val sqlDelightDriver = DriverFactory(this@MainActivity).createDriver()
21 | DelightSqlViewer.init(object : SqlDelightEnvironmentProvider() {
22 | override fun getDriver() = sqlDelightDriver
23 | override fun getContext() = this@MainActivity
24 | })
25 | }
26 |
27 | override fun initRoom() {
28 | val roomDatabase = AppDatabase.create(DatabaseBuilder(this@MainActivity).createBuilder())
29 | DelightSqlViewer.init(object : RoomEnvironmentProvider() {
30 | override fun getDriver() = roomDatabase
31 | override fun getContext() = this@MainActivity
32 | })
33 | }
34 | }
35 | // Initialize SqlDelight for shortcut
36 | databaseInitializer.initSqlDelight()
37 | setContent {
38 | App(databaseInitializer = databaseInitializer)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/sample/android/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/sample/desktop/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("multiplatform")
3 | alias(libs.plugins.compose.compiler)
4 | alias(libs.plugins.jetbrainsCompose)
5 | }
6 |
7 | kotlin {
8 | jvm()
9 | sourceSets {
10 | val jvmMain by getting {
11 | dependencies {
12 | implementation(projects.shared)
13 | implementation(compose.desktop.currentOs)
14 | implementation(libs.kotlinx.coroutines.swing)
15 | }
16 | }
17 | val jvmTest by getting
18 | }
19 | }
20 |
21 | compose.desktop {
22 | application {
23 | mainClass = "ru.bartwell.delightsqlviewer.sample.desktop.MainKt"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sample/desktop/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/sample/desktop/Main.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.desktop
2 |
3 | import androidx.compose.ui.Alignment
4 | import androidx.compose.ui.unit.dp
5 | import androidx.compose.ui.window.Window
6 | import androidx.compose.ui.window.WindowPosition
7 | import androidx.compose.ui.window.application
8 | import androidx.compose.ui.window.rememberWindowState
9 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
10 | import ru.bartwell.delightsqlviewer.adapter.room.RoomEnvironmentProvider
11 | import ru.bartwell.delightsqlviewer.adapter.sqldelight.SqlDelightEnvironmentProvider
12 | import ru.bartwell.delightsqlviewer.sample.shared.App
13 | import ru.bartwell.delightsqlviewer.sample.shared.DatabaseInitializer
14 | import ru.bartwell.delightsqlviewer.sample.shared.database.room.AppDatabase
15 | import ru.bartwell.delightsqlviewer.sample.shared.database.room.DatabaseBuilder
16 | import ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight.DriverFactory
17 |
18 | fun main() = application {
19 | val databaseInitializer = object : DatabaseInitializer {
20 | override fun initSqlDelight() {
21 | val sqlDelightDriver = DriverFactory().createDriver()
22 | DelightSqlViewer.init(object : SqlDelightEnvironmentProvider() {
23 | override fun getDriver() = sqlDelightDriver
24 | })
25 | }
26 |
27 | override fun initRoom() {
28 | val roomDatabase = AppDatabase.create(DatabaseBuilder().createBuilder())
29 | DelightSqlViewer.init(object : RoomEnvironmentProvider() {
30 | override fun getDriver() = roomDatabase
31 | })
32 | }
33 | }
34 | Window(
35 | title = "Wender",
36 | resizable = false,
37 | state = rememberWindowState(
38 | width = 600.dp,
39 | height = 600.dp,
40 | position = WindowPosition.Aligned(Alignment.Center),
41 | ),
42 | onCloseRequest = ::exitApplication,
43 | ) {
44 | App(databaseInitializer = databaseInitializer)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/sample/ios/iosSample.xcodeproj/xcshareddata/xcschemes/iosSample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/sample/ios/iosSample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/ios/iosSample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import shared
3 |
4 | class AppDelegate: NSObject, UIApplicationDelegate {
5 | func application(
6 | _ application: UIApplication,
7 | configurationForConnecting connectingSceneSession: UISceneSession,
8 | options: UIScene.ConnectionOptions
9 | ) -> UISceneConfiguration {
10 | return ShortcutActionHandler.shared.getConfiguration(session: connectingSceneSession)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/ios/iosSample/AppTheme.swift:
--------------------------------------------------------------------------------
1 | import shared
2 |
3 | enum AppTheme: String, CaseIterable, Identifiable {
4 | case auto = "Auto"
5 | case dark = "Dark"
6 | case light = "Light"
7 |
8 | var id: String { rawValue }
9 |
10 | func toLibraryTheme() -> Theme {
11 | switch self {
12 | case .auto:
13 | return Theme.Auto()
14 | case .dark:
15 | return Theme.Dark()
16 | case .light:
17 | return Theme.Light()
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sample/ios/iosSample/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 | }
--------------------------------------------------------------------------------
/sample/ios/iosSample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
--------------------------------------------------------------------------------
/sample/ios/iosSample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/sample/ios/iosSample/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import shared
3 |
4 | enum DatabaseType: String, CaseIterable, Identifiable {
5 | case sqlDelight = "SqlDelight"
6 | case room = "Room"
7 |
8 | var id: String { self.rawValue }
9 | }
10 |
11 | struct ContentView: View {
12 | let databaseInitializer: DatabaseInitializer
13 | @State private var selectedDatabase: DatabaseType = .sqlDelight
14 | @State private var selectedTheme: AppTheme = .auto
15 |
16 | var body: some View {
17 | VStack(spacing: 20) {
18 | Picker("Select database", selection: $selectedDatabase) {
19 | ForEach(DatabaseType.allCases) { db in
20 | Text(db.rawValue).tag(db)
21 | }
22 | }
23 | .pickerStyle(SegmentedPickerStyle())
24 | .padding()
25 |
26 | Picker("Select theme", selection: $selectedTheme) {
27 | ForEach(AppTheme.allCases) { theme in
28 | Text(theme.rawValue).tag(theme)
29 | }
30 | }
31 | .pickerStyle(SegmentedPickerStyle())
32 | .padding()
33 |
34 | Button("Launch viewer") {
35 | launchViewer()
36 | }
37 | .buttonStyle(.borderedProminent)
38 | .padding()
39 | }
40 | .padding()
41 | .preferredColorScheme(colorScheme(for: selectedTheme))
42 | }
43 |
44 | private func colorScheme(for theme: AppTheme) -> ColorScheme? {
45 | switch theme {
46 | case .auto:
47 | return nil
48 | case .dark:
49 | return .dark
50 | case .light:
51 | return .light
52 | }
53 | }
54 |
55 | func launchViewer() {
56 | switch selectedDatabase {
57 | case .sqlDelight:
58 | databaseInitializer.doInitSqlDelight()
59 | case .room:
60 | databaseInitializer.doInitRoom()
61 | }
62 | DelightSqlViewer.shared.launch(theme: selectedTheme.toLibraryTheme())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/sample/ios/iosSample/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 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIRequiredDeviceCapabilities
29 |
30 | armv7
31 |
32 | UISupportedInterfaceOrientations
33 |
34 | UIInterfaceOrientationPortrait
35 | UIInterfaceOrientationLandscapeLeft
36 | UIInterfaceOrientationLandscapeRight
37 |
38 | UISupportedInterfaceOrientations~ipad
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationPortraitUpsideDown
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 | UILaunchScreen
46 |
47 |
48 |
--------------------------------------------------------------------------------
/sample/ios/iosSample/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/sample/ios/iosSample/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import shared
3 |
4 | class DatabaseInitializerImpl: DatabaseInitializer {
5 |
6 | init () {
7 | // Initialize SqlDelight for shortcut
8 | doInitSqlDelight()
9 | }
10 |
11 | func doInitSqlDelight() {
12 | let sqlDriver = DriverFactory().createDriver()
13 | let provider = SqlDelightEnvironmentProvider(driver: sqlDriver)
14 | DelightSqlViewer.shared.doInit(provider: provider, isShortcutEnabled: true)
15 | }
16 |
17 | func doInitRoom() {
18 | let databaseBuilder = DatabaseBuilder().createBuilder()
19 | let roomDatabase = AppDatabase.companion.create(builder: databaseBuilder)
20 | let provider = RoomEnvironmentProvider(driver: roomDatabase)
21 | DelightSqlViewer.shared.doInit(provider: provider, isShortcutEnabled: true)
22 | }
23 | }
24 |
25 | @main
26 | struct iOSApp: App {
27 | @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
28 | let databaseInitializer: DatabaseInitializer = DatabaseInitializerImpl()
29 | var body: some Scene {
30 | WindowGroup {
31 | ContentView(databaseInitializer: databaseInitializer)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sample/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | alias(libs.plugins.jetbrainsCompose)
7 | alias(libs.plugins.ksp)
8 | alias(libs.plugins.compose.compiler)
9 | alias(libs.plugins.kotlinSerialization)
10 | alias(libs.plugins.sqldelight)
11 | alias(libs.plugins.room)
12 | }
13 |
14 | /*
15 | The isRelease flag is created here to demonstrate one possible way to switch between
16 | release and debug builds. In release builds, we use the full-featured library, while in
17 | debug builds, we use a stub library.
18 | */
19 | val isRelease = extra["isRelease"] as Boolean
20 |
21 | kotlin {
22 | androidTarget {
23 | compilations.all {
24 | compileTaskProvider.configure {
25 | compilerOptions {
26 | jvmTarget.set(JvmTarget.JVM_1_8)
27 | }
28 | }
29 | }
30 | }
31 |
32 | listOf(
33 | iosX64(),
34 | iosArm64(),
35 | iosSimulatorArm64()
36 | ).forEach {
37 | it.binaries.framework {
38 | baseName = "shared"
39 | isStatic = true
40 | if (isRelease) {
41 | export(projects.stub)
42 | } else {
43 | export(projects.runtime)
44 | export(projects.core)
45 | export(projects.sqldelightAdapter)
46 | export(projects.roomAdapter)
47 | }
48 | }
49 | }
50 |
51 | jvm()
52 |
53 | sourceSets {
54 | commonMain.dependencies {
55 | if (isRelease) {
56 | api(projects.stub)
57 | } else {
58 | api(projects.runtime)
59 | api(projects.core)
60 | api(projects.sqldelightAdapter)
61 | api(projects.roomAdapter)
62 | }
63 | implementation(compose.runtime)
64 | implementation(compose.foundation)
65 | implementation(compose.material3)
66 | implementation(compose.materialIconsExtended)
67 | implementation(libs.decompose)
68 | implementation(libs.decompose.extensions.compose)
69 | api(libs.room.runtime)
70 | implementation(libs.room.driver)
71 | implementation(libs.kotlinx.coroutines.core)
72 | }
73 | commonTest.dependencies {
74 | implementation(libs.kotlin.test)
75 | }
76 | androidMain.dependencies {
77 | implementation(libs.androidx.activity.compose)
78 | implementation(libs.sqldelight.android.driver)
79 | }
80 | iosMain.dependencies {
81 | implementation(libs.sqldelight.native.driver)
82 | }
83 | jvmMain.dependencies {
84 | implementation(compose.desktop.currentOs)
85 | implementation(libs.sqldelight.driver.sqlite)
86 | }
87 | }
88 | }
89 |
90 | android {
91 | namespace = "ru.bartwell.delightsqlviewer.sample.shared"
92 | compileSdk = 35
93 |
94 | defaultConfig {
95 | minSdk = 24
96 | }
97 |
98 | compileOptions {
99 | sourceCompatibility = JavaVersion.VERSION_1_8
100 | targetCompatibility = JavaVersion.VERSION_1_8
101 | }
102 |
103 | buildFeatures {
104 | compose = true
105 | }
106 | }
107 |
108 | dependencies {
109 | add("kspAndroid", libs.room.compiler)
110 | add("kspIosSimulatorArm64", libs.room.compiler)
111 | add("kspIosX64", libs.room.compiler)
112 | add("kspIosArm64", libs.room.compiler)
113 | add("kspJvm", libs.room.compiler)
114 | }
115 |
116 | room {
117 | schemaDirectory("$projectDir/schemas")
118 | }
119 |
120 | sqldelight {
121 | databases {
122 | create("SampleDatabase") {
123 | packageName.set("ru.bartwell.delightsqlviewer.sample.shared")
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/sample/shared/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import androidx.room.RoomDatabase
6 |
7 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
8 | actual class DatabaseBuilder(private val context: Context) {
9 | actual fun createBuilder(): RoomDatabase.Builder {
10 | val appContext = context.applicationContext
11 | val dbFile = appContext.getDatabasePath("sample_room.db")
12 | return Room.databaseBuilder(context = appContext, name = dbFile.absolutePath)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/sample/shared/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/sqldelight/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight
2 |
3 | import android.content.Context
4 | import app.cash.sqldelight.db.SqlDriver
5 | import app.cash.sqldelight.driver.android.AndroidSqliteDriver
6 | import ru.bartwell.delightsqlviewer.sample.shared.SampleDatabase
7 |
8 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
9 | actual class DriverFactory(private val context: Context) {
10 | actual fun createDriver(): SqlDriver {
11 | return AndroidSqliteDriver(SampleDatabase.Schema, context, "sample.db")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/App.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.isSystemInDarkTheme
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.IntrinsicSize
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.Spacer
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.fillMaxWidth
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.layout.width
16 | import androidx.compose.material3.Button
17 | import androidx.compose.material3.ColorScheme
18 | import androidx.compose.material3.DropdownMenuItem
19 | import androidx.compose.material3.ExperimentalMaterial3Api
20 | import androidx.compose.material3.ExposedDropdownMenuBox
21 | import androidx.compose.material3.ExposedDropdownMenuDefaults
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.MenuAnchorType
24 | import androidx.compose.material3.RadioButton
25 | import androidx.compose.material3.Surface
26 | import androidx.compose.material3.Text
27 | import androidx.compose.material3.TextField
28 | import androidx.compose.material3.darkColorScheme
29 | import androidx.compose.material3.lightColorScheme
30 | import androidx.compose.runtime.Composable
31 | import androidx.compose.runtime.getValue
32 | import androidx.compose.runtime.mutableStateOf
33 | import androidx.compose.runtime.remember
34 | import androidx.compose.runtime.setValue
35 | import androidx.compose.ui.Alignment
36 | import androidx.compose.ui.Modifier
37 | import androidx.compose.ui.unit.dp
38 | import ru.bartwell.delightsqlviewer.DelightSqlViewer
39 | import ru.bartwell.delightsqlviewer.core.data.Theme
40 |
41 | @Composable
42 | fun App(
43 | databaseInitializer: DatabaseInitializer,
44 | ) {
45 | var selectedDatabase by remember { mutableStateOf(Database.SqlDelight) }
46 | var selectedTheme by remember { mutableStateOf(AppTheme.Auto) }
47 | MaterialTheme(
48 | colorScheme = selectedTheme.getColorScheme(),
49 | ) {
50 | Surface(
51 | modifier = Modifier.fillMaxSize(),
52 | color = MaterialTheme.colorScheme.background,
53 | ) {
54 | Box(
55 | modifier = Modifier.fillMaxSize(),
56 | contentAlignment = Alignment.Center,
57 | ) {
58 | Column(horizontalAlignment = Alignment.CenterHorizontally) {
59 | Column(modifier = Modifier.width(IntrinsicSize.Max)) {
60 | Database.entries.forEach { entry ->
61 | RadioButtonRow(
62 | title = entry.name,
63 | isSelected = entry == selectedDatabase,
64 | onClick = { selectedDatabase = entry }
65 | )
66 | }
67 | }
68 | Spacer(modifier = Modifier.height(32.dp))
69 | ThemeList(selected = selectedTheme, onSelect = { selectedTheme = it })
70 | Spacer(modifier = Modifier.height(32.dp))
71 | Button(
72 | onClick = {
73 | when (selectedDatabase) {
74 | Database.SqlDelight -> databaseInitializer.initSqlDelight()
75 | Database.Room -> databaseInitializer.initRoom()
76 | }
77 | DelightSqlViewer.launch(theme = selectedTheme.toLibraryTheme())
78 | },
79 | content = {
80 | Text(text = "Launch viewer")
81 | }
82 | )
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | @Composable
90 | private fun RadioButtonRow(title: String, isSelected: Boolean, onClick: () -> Unit) {
91 | Row(
92 | modifier = Modifier.fillMaxWidth()
93 | .clickable(onClick = onClick)
94 | .padding(16.dp),
95 | horizontalArrangement = Arrangement.SpaceBetween,
96 | verticalAlignment = Alignment.CenterVertically
97 | ) {
98 | Text(text = title)
99 | Spacer(modifier = Modifier.width(16.dp))
100 | RadioButton(selected = isSelected, onClick = null)
101 | }
102 | }
103 |
104 | @OptIn(ExperimentalMaterial3Api::class)
105 | @Composable
106 | private fun ThemeList(
107 | selected: AppTheme,
108 | onSelect: (AppTheme) -> Unit,
109 | ) {
110 | var expanded by remember { mutableStateOf(false) }
111 | val themeOptions = AppTheme.entries.map { it.name }
112 | ExposedDropdownMenuBox(
113 | expanded = expanded,
114 | onExpandedChange = { expanded = !expanded }
115 | ) {
116 | TextField(
117 | value = selected.name,
118 | onValueChange = { },
119 | label = { Text("Theme") },
120 | readOnly = true,
121 | trailingIcon = {
122 | ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
123 | },
124 | colors = ExposedDropdownMenuDefaults.textFieldColors(),
125 | modifier = Modifier.menuAnchor(type = MenuAnchorType.PrimaryNotEditable),
126 | )
127 | ExposedDropdownMenu(
128 | expanded = expanded,
129 | onDismissRequest = { expanded = false }
130 | ) {
131 | themeOptions.forEach { selectionOption ->
132 | DropdownMenuItem(
133 | text = { Text(selectionOption) },
134 | onClick = {
135 | onSelect(AppTheme.valueOf(selectionOption))
136 | expanded = false
137 | }
138 | )
139 | }
140 | }
141 | }
142 | }
143 |
144 | private enum class Database {
145 | SqlDelight, Room
146 | }
147 |
148 | private enum class AppTheme {
149 | Auto, Dark, Light, Custom;
150 |
151 | @Composable
152 | fun getColorScheme(): ColorScheme = when (this) {
153 | Auto -> if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
154 | Dark -> darkColorScheme()
155 | Light -> lightColorScheme()
156 | Custom -> appCustomColorScheme()
157 | }
158 | }
159 |
160 | private fun AppTheme.toLibraryTheme(): Theme = when (this) {
161 | AppTheme.Auto -> Theme.Auto
162 | AppTheme.Dark -> Theme.Dark
163 | AppTheme.Light -> Theme.Light
164 | AppTheme.Custom -> Theme.Custom(appCustomColorScheme())
165 | }
166 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/AppCustomColorScheme.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared
2 |
3 | import androidx.compose.material3.ColorScheme
4 | import androidx.compose.ui.graphics.Color
5 |
6 | @Suppress("MagicNumber")
7 | fun appCustomColorScheme() = ColorScheme(
8 | primary = Color(0xFF2E7D32),
9 | onPrimary = Color(0xFFFFFFFF),
10 | primaryContainer = Color(0xFFA5D6A7),
11 | onPrimaryContainer = Color(0xFF1B5E20),
12 | inversePrimary = Color(0xFF66BB6A),
13 | secondary = Color(0xFF0288D1),
14 | onSecondary = Color(0xFFFFFFFF),
15 | secondaryContainer = Color(0xFFB3E5FC),
16 | onSecondaryContainer = Color(0xFF01579B),
17 | tertiary = Color(0xFFEF6C00),
18 | onTertiary = Color(0xFFFFFFFF),
19 | tertiaryContainer = Color(0xFFFFCC80),
20 | onTertiaryContainer = Color(0xFFE65100),
21 | background = Color(0xFFFFFDE7),
22 | onBackground = Color(0xFF3E2723),
23 | surface = Color(0xFFFFF8E1),
24 | onSurface = Color(0xFF5D4037),
25 | surfaceVariant = Color(0xFFCFD8DC),
26 | onSurfaceVariant = Color(0xFF37474F),
27 | surfaceTint = Color(0xFF2E7D32),
28 | inverseSurface = Color(0xFF424242),
29 | inverseOnSurface = Color(0xFFFAFAFA),
30 | error = Color(0xFFB00020),
31 | onError = Color(0xFFFFFFFF),
32 | errorContainer = Color(0xFFFFCDD2),
33 | onErrorContainer = Color(0xFFB00020),
34 | outline = Color(0xFF9E9E9E),
35 | outlineVariant = Color(0xFFBDBDBD),
36 | scrim = Color(0xFF000000),
37 | surfaceBright = Color(0xFFFFFFFF),
38 | surfaceDim = Color(0xFFF5F5F5),
39 | surfaceContainer = Color(0xFFFFF8E1),
40 | surfaceContainerHigh = Color(0xFFFFF9C4),
41 | surfaceContainerHighest = Color(0xFFFFFFFF),
42 | surfaceContainerLow = Color(0xFFFFF3E0),
43 | surfaceContainerLowest = Color(0xFFFFF9E0)
44 | )
45 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/DatabaseInitializer.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared
2 |
3 | interface DatabaseInitializer {
4 | fun initSqlDelight()
5 | fun initRoom()
6 | }
7 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/Database.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.ConstructedBy
4 | import androidx.room.Database
5 | import androidx.room.RoomDatabase
6 | import androidx.room.RoomDatabaseConstructor
7 | import androidx.sqlite.SQLiteConnection
8 | import androidx.sqlite.driver.bundled.BundledSQLiteDriver
9 | import androidx.sqlite.execSQL
10 | import kotlinx.coroutines.Dispatchers
11 |
12 | @Database(entities = [Table1Entity::class, Table2Entity::class], version = 1)
13 | @ConstructedBy(AppDatabaseConstructor::class)
14 | abstract class AppDatabase : RoomDatabase() {
15 | abstract fun getTable1Dao(): Table1Dao
16 | abstract fun getTable2Dao(): Table2Dao
17 |
18 | companion object {
19 | fun create(builder: Builder): AppDatabase {
20 | return builder
21 | .addCallback(object : Callback() {
22 | override fun onCreate(connection: SQLiteConnection) {
23 | super.onCreate(connection)
24 | connection.execSQL(
25 | """
26 | INSERT INTO table1 (name, age, salary, data)
27 | VALUES
28 | ('Alexandria Catherine Montgomery-Bentley-Smythe-Wilkinson-Johnson', 25, 50000.50, CAST('12345678' AS BLOB)),
29 | ('Bob Smith', 30, 62000.00, CAST('abcdef12' AS BLOB)),
30 | ('Charlie Brown', 28, NULL, NULL),
31 | ('David Miller', 40, 75000.25, CAST('789abcde' AS BLOB)),
32 | ('Eve Davis', 35, 82000.75, NULL),
33 | ('Frank Wilson', 45, 95000.00, CAST('456def78' AS BLOB)),
34 | ('Grace Lee', 29, 67000.10, CAST('zxcvbnm' AS BLOB)),
35 | ('Hank Anderson', 50, NULL, NULL),
36 | ('Ivy Thompson', 22, 45000.90, CAST('654321006543210065432100654321006543210065432100654321006543210065432100' AS BLOB)),
37 | ('Jack Peterson', 38, 78000.40, NULL)
38 | """.trimIndent()
39 | )
40 |
41 | connection.execSQL(
42 | """
43 | INSERT INTO table2 (title, quantity, price, image)
44 | VALUES
45 | ('Product A', 100, 9.99, CAST('AABBCCDD' AS BLOB)),
46 | ('Product B', 50, 19.95, CAST('11223344' AS BLOB)),
47 | ('Product C', 75, 14.50, CAST('DEADBEEF' AS BLOB)),
48 | ('Product D', 200, 4.99, CAST('CAFEBABE' AS BLOB)),
49 | ('Product E', 120, 29.95, CAST('FEEDFACE' AS BLOB)),
50 | ('Product F', 30, 49.99, CAST('BADDCAFE' AS BLOB)),
51 | ('Product G', 90, 5.50, CAST('FACEFEED' AS BLOB)),
52 | ('Product H', 60, 12.00, CAST('DEADC0DE' AS BLOB)),
53 | ('Product I', 15, 99.95, CAST('C0FFEE00' AS BLOB)),
54 | ('Product J', 80, 7.25, CAST('0BADF00D' AS BLOB))
55 | """.trimIndent()
56 | )
57 | }
58 | })
59 | .fallbackToDestructiveMigration(dropAllTables = true)
60 | .setDriver(BundledSQLiteDriver())
61 | .setQueryCoroutineContext(Dispatchers.Default)
62 | .build()
63 | }
64 | }
65 | }
66 |
67 | @Suppress("NO_ACTUAL_FOR_EXPECT", "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
68 | expect object AppDatabaseConstructor : RoomDatabaseConstructor {
69 | override fun initialize(): AppDatabase
70 | }
71 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.RoomDatabase
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | expect class DatabaseBuilder {
7 | fun createBuilder(): RoomDatabase.Builder
8 | }
9 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/Table1Dao.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Dao
4 |
5 | @Dao
6 | interface Table1Dao
7 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/Table1Entity.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "table1")
7 | data class Table1Entity(
8 | @PrimaryKey(autoGenerate = true)
9 | val id: Long = 0,
10 | val name: String? = null,
11 | val age: Int? = null,
12 | val salary: Double? = null,
13 | val data: ByteArray? = null
14 | )
15 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/Table2Dao.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Dao
4 |
5 | @Dao
6 | interface Table2Dao
7 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/Table2Entity.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "table2")
7 | data class Table2Entity(
8 | @PrimaryKey(autoGenerate = true)
9 | val id: Long = 0,
10 | val title: String? = null,
11 | val quantity: Int? = null,
12 | val price: Double? = null,
13 | val image: ByteArray? = null
14 | )
15 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/sqldelight/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 |
5 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6 | expect class DriverFactory {
7 | fun createDriver(): SqlDriver
8 | }
9 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/sqldelight/ru/bartwell/delightsqlviewer/sample/shared/table1.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE table1 (
2 | id INTEGER PRIMARY KEY AUTOINCREMENT,
3 | name TEXT NOT NULL DEFAULT 'Unknown',
4 | age INTEGER,
5 | salary REAL,
6 | data BLOB
7 | );
8 |
9 | INSERT INTO table1 (name, age, salary, data) VALUES ('Alexandria Catherine Montgomery-Bentley-Smythe-Wilkinson-Johnson', 25, 50000.50, CAST('12345678' AS BLOB));
10 | INSERT INTO table1 (name, age, salary, data) VALUES ('Bob Smith', 30, 62000.00, CAST('abcdef12' AS BLOB));
11 | INSERT INTO table1 (name, age, salary, data) VALUES ('Charlie Brown', 28, NULL, NULL);
12 | INSERT INTO table1 (name, age, salary, data) VALUES ('David Miller', 40, 75000.25, CAST('789abcde' AS BLOB));
13 | INSERT INTO table1 (name, age, salary, data) VALUES ('Eve Davis', 35, 82000.75, NULL);
14 | INSERT INTO table1 (name, age, salary, data) VALUES ('Frank Wilson', 45, 95000.00, CAST('456def78' AS BLOB));
15 | INSERT INTO table1 (name, age, salary, data) VALUES ('Grace Lee', 29, 67000.10, CAST('zxcvbnm' AS BLOB));
16 | INSERT INTO table1 (name, age, salary, data) VALUES ('Hank Anderson', 50, NULL, NULL);
17 | INSERT INTO table1 (name, age, salary, data) VALUES ('Ivy Thompson', 22, 45000.90, CAST('654321006543210065432100654321006543210065432100654321006543210065432100' AS BLOB));
18 | INSERT INTO table1 (name, age, salary, data) VALUES ('Jack Peterson', 38, 78000.40, NULL);
19 |
--------------------------------------------------------------------------------
/sample/shared/src/commonMain/sqldelight/ru/bartwell/delightsqlviewer/sample/shared/table2.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE table2 (
2 | id INTEGER PRIMARY KEY AUTOINCREMENT,
3 | title TEXT NOT NULL DEFAULT '',
4 | quantity INTEGER NOT NULL DEFAULT 0,
5 | price REAL NOT NULL DEFAULT 0,
6 | image BLOB NOT NULL DEFAULT 1234
7 | );
8 |
9 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product A', 100, 9.99, CAST('AABBCCDD' AS BLOB));
10 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product B', 50, 19.95, CAST('11223344' AS BLOB));
11 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product C', 75, 14.50, CAST('DEADBEEF' AS BLOB));
12 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product D', 200, 4.99, CAST('CAFEBABE' AS BLOB));
13 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product E', 120, 29.95, CAST('FEEDFACE' AS BLOB));
14 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product F', 30, 49.99, CAST('BADDCAFE' AS BLOB));
15 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product G', 90, 5.50, CAST('FACEFEED' AS BLOB));
16 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product H', 60, 12.00, CAST('DEADC0DE' AS BLOB));
17 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product I', 15, 99.95, CAST('C0FFEE00' AS BLOB));
18 | INSERT INTO table2 (title, quantity, price, image) VALUES ('Product J', 80, 7.25, CAST('0BADF00D' AS BLOB));
19 |
--------------------------------------------------------------------------------
/sample/shared/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Room
4 | import androidx.room.RoomDatabase
5 | import kotlinx.cinterop.ExperimentalForeignApi
6 | import platform.Foundation.NSDocumentDirectory
7 | import platform.Foundation.NSFileManager
8 | import platform.Foundation.NSUserDomainMask
9 |
10 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
11 | actual class DatabaseBuilder {
12 | actual fun createBuilder(): RoomDatabase.Builder {
13 | val dbFilePath = documentDirectory() + "/sample_room.db"
14 | return Room.databaseBuilder(name = dbFilePath,)
15 | }
16 |
17 | @OptIn(ExperimentalForeignApi::class)
18 | private fun documentDirectory(): String {
19 | val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
20 | directory = NSDocumentDirectory,
21 | inDomain = NSUserDomainMask,
22 | appropriateForURL = null,
23 | create = false,
24 | error = null,
25 | )
26 | return requireNotNull(documentDirectory?.path)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sample/shared/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/sqldelight/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import app.cash.sqldelight.driver.native.NativeSqliteDriver
5 | import ru.bartwell.delightsqlviewer.sample.shared.SampleDatabase
6 |
7 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
8 | actual class DriverFactory {
9 | actual fun createDriver(): SqlDriver {
10 | return NativeSqliteDriver(SampleDatabase.Schema, "sample.db")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample/shared/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/room/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.room
2 |
3 | import androidx.room.Room
4 | import androidx.room.RoomDatabase
5 | import java.io.File
6 |
7 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
8 | actual class DatabaseBuilder {
9 | actual fun createBuilder(): RoomDatabase.Builder {
10 | val dbFile = File(System.getProperty("java.io.tmpdir"), "sample_room.db")
11 | return Room.databaseBuilder(name = dbFile.absolutePath)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/sample/shared/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/sample/shared/database/sqldelight/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.sample.shared.database.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
5 | import ru.bartwell.delightsqlviewer.sample.shared.SampleDatabase
6 | import java.util.Properties
7 |
8 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
9 | actual class DriverFactory {
10 | actual fun createDriver(): SqlDriver {
11 | return JdbcSqliteDriver("jdbc:sqlite:sample.db", Properties(), SampleDatabase.Schema)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
2 | pluginManagement {
3 | repositories {
4 | google()
5 | gradlePluginPortal()
6 | mavenCentral()
7 | }
8 | }
9 |
10 | dependencyResolutionManagement {
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | }
16 |
17 | rootProject.name = "DelightSQLViewer"
18 | include("android")
19 | project(":android").projectDir = file("sample/android")
20 | include("desktop")
21 | project(":desktop").projectDir = file("sample/desktop")
22 | include("shared")
23 | project(":shared").projectDir = file("sample/shared")
24 | include(":runtime")
25 | include(":stub")
26 | include(":core")
27 | include(":sqldelight-adapter")
28 | include(":room-adapter")
29 |
--------------------------------------------------------------------------------
/settings.properties:
--------------------------------------------------------------------------------
1 | isRelease=false
2 |
--------------------------------------------------------------------------------
/sqldelight-adapter/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | alias(libs.plugins.sqldelight)
7 | id("maven-publish")
8 | id("signing")
9 | }
10 |
11 | group = "ru.bartwell.delightsqlviewer"
12 | version = extra["libraryVersionName"] as String
13 |
14 | kotlin {
15 | androidTarget {
16 | compilations.all {
17 | compileTaskProvider.configure {
18 | compilerOptions {
19 | jvmTarget.set(JvmTarget.JVM_1_8)
20 | }
21 | }
22 | }
23 | publishLibraryVariants("release")
24 | }
25 |
26 | listOf(
27 | iosX64(),
28 | iosArm64(),
29 | iosSimulatorArm64()
30 | ).forEach {
31 | it.binaries.framework {
32 | baseName = "sqldelight-adapter"
33 | isStatic = true
34 | }
35 | }
36 |
37 | jvm()
38 |
39 | sourceSets {
40 | commonMain.dependencies {
41 | implementation(projects.core)
42 | implementation(libs.kotlinx.coroutines.core)
43 | }
44 | }
45 |
46 | explicitApi()
47 | }
48 |
49 | android {
50 | namespace = "ru.bartwell.delightsqlviewer"
51 | compileSdk = 34
52 |
53 | defaultConfig {
54 | minSdk = 24
55 | }
56 |
57 | compileOptions {
58 | sourceCompatibility = JavaVersion.VERSION_1_8
59 | targetCompatibility = JavaVersion.VERSION_1_8
60 | }
61 | }
62 |
63 | apply(from = "$rootDir/gradle/maven-publishing.gradle.kts")
64 |
--------------------------------------------------------------------------------
/sqldelight-adapter/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
6 |
7 | public abstract class SqlDelightEnvironmentProvider : AndroidEnvironmentProvider {
8 | final override fun getWrapper(): DatabaseWrapper = SqlDelightWrapper(getDriver())
9 | }
10 |
--------------------------------------------------------------------------------
/sqldelight-adapter/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightCursorWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlCursor
4 | import ru.bartwell.delightsqlviewer.core.mapper.CursorWrapper
5 |
6 | internal class SqlDelightCursorWrapper(override val value: SqlCursor) : CursorWrapper {
7 | override fun getString(index: Int): String? = value.getString(index)
8 | override fun getBoolean(index: Int): Boolean? = value.getBoolean(index)
9 | override fun getLong(index: Int): Long? = value.getLong(index)
10 | override fun getDouble(index: Int): Double? = value.getDouble(index)
11 | override fun getBytes(index: Int): ByteArray? = value.getBytes(index)
12 | }
13 |
--------------------------------------------------------------------------------
/sqldelight-adapter/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightWrapper.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import app.cash.sqldelight.db.QueryResult
4 | import app.cash.sqldelight.db.SqlDriver
5 | import app.cash.sqldelight.db.SqlPreparedStatement
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.flow
8 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
9 | import ru.bartwell.delightsqlviewer.core.data.Column
10 | import ru.bartwell.delightsqlviewer.core.data.ColumnType
11 | import ru.bartwell.delightsqlviewer.core.mapper.SqlMapper
12 |
13 | public class SqlDelightWrapper(private val driver: SqlDriver) : DatabaseWrapper() {
14 |
15 | override fun query(sql: String, mapper: SqlMapper): Flow> = flow {
16 | val result = driver.executeQuery(
17 | identifier = null,
18 | sql = sql,
19 | parameters = 0,
20 | mapper = { cursor ->
21 | QueryResult.Value(
22 | buildList {
23 | while (cursor.next().value) {
24 | mapper.map(SqlDelightCursorWrapper(cursor))?.let { add(it) }
25 | }
26 | }
27 | )
28 | }
29 | ).value
30 | emit(result)
31 | }
32 |
33 | override fun querySingle(sql: String, mapper: SqlMapper): Flow = flow {
34 | val result = driver.executeQuery(
35 | identifier = null,
36 | sql = sql,
37 | parameters = 0,
38 | mapper = { cursor ->
39 | QueryResult.Value(
40 | buildList {
41 | val value = if (cursor.next().value) {
42 | mapper.map(SqlDelightCursorWrapper(cursor))
43 | } else {
44 | null
45 | }
46 | add(value)
47 | }
48 | )
49 | }
50 | )
51 | .value
52 | .first()
53 | emit(result)
54 | }
55 |
56 | @OptIn(ExperimentalStdlibApi::class)
57 | override fun updateSingle(table: String, id: Long, column: Column, value: String?): Flow = flow {
58 | val sql = buildUpdateQuery(table, column)
59 | driver.execute(
60 | identifier = null,
61 | sql = sql,
62 | parameters = 2,
63 | binders = {
64 | bindValue(index = 0, column = column, value = value)
65 | bindLong(index = 1, long = id)
66 | }
67 | )
68 | emit(Unit)
69 | }
70 |
71 | @OptIn(ExperimentalStdlibApi::class)
72 | public override fun insert(table: String, values: Map): Flow = flow {
73 | val sql = buildInsertQuery(table, values)
74 | driver.execute(
75 | identifier = null,
76 | sql = sql,
77 | parameters = values.size,
78 | binders = {
79 | if (values.isNotEmpty()) {
80 | for ((index, entry) in values.entries.withIndex()) {
81 | bindValue(index = index, column = entry.key, value = entry.value)
82 | }
83 | }
84 | }
85 | )
86 | emit(Unit)
87 | }
88 |
89 | public override fun delete(table: String, ids: List): Flow = flow {
90 | if (ids.isNotEmpty()) {
91 | val sql = buildDeleteQuery(table, ids)
92 | driver.execute(identifier = null, sql = sql, parameters = 0)
93 | }
94 | emit(Unit)
95 | }
96 | }
97 |
98 | @OptIn(ExperimentalStdlibApi::class)
99 | private fun SqlPreparedStatement.bindValue(index: Int, column: Column, value: String?) {
100 | when (column.type) {
101 | ColumnType.INTEGER -> bindLong(index, value?.toLong())
102 | ColumnType.TEXT -> bindString(index, value)
103 | ColumnType.REAL -> bindDouble(index, value?.toDouble())
104 | ColumnType.BLOB -> bindBytes(index, value?.hexToByteArray())
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/sqldelight-adapter/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import ru.bartwell.delightsqlviewer.IosEnvironmentProvider
5 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
6 |
7 | public class SqlDelightEnvironmentProvider(driver: SqlDriver) : IosEnvironmentProvider(driver) {
8 | override fun getWrapper(): DatabaseWrapper = SqlDelightWrapper(driver)
9 | }
10 |
--------------------------------------------------------------------------------
/sqldelight-adapter/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import ru.bartwell.delightsqlviewer.core.DatabaseWrapper
5 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
6 |
7 | public abstract class SqlDelightEnvironmentProvider : EnvironmentProvider {
8 | final override fun getWrapper(): DatabaseWrapper = SqlDelightWrapper(getDriver())
9 | }
10 |
--------------------------------------------------------------------------------
/stub/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.kotlinMultiplatform)
5 | alias(libs.plugins.androidLibrary)
6 | id("maven-publish")
7 | id("signing")
8 | }
9 |
10 | group = "ru.bartwell.delightsqlviewer"
11 | version = extra["libraryVersionName"] as String
12 |
13 | kotlin {
14 | androidTarget {
15 | compilations.all {
16 | compileTaskProvider.configure {
17 | compilerOptions {
18 | jvmTarget.set(JvmTarget.JVM_1_8)
19 | }
20 | }
21 | }
22 | publishLibraryVariants("release")
23 | }
24 |
25 | listOf(
26 | iosX64(),
27 | iosArm64(),
28 | iosSimulatorArm64()
29 | ).forEach {
30 | it.binaries.framework {
31 | baseName = "stub"
32 | isStatic = true
33 | }
34 | }
35 |
36 | jvm()
37 |
38 | explicitApi()
39 | }
40 |
41 | android {
42 | namespace = "ru.bartwell.delightsqlviewer"
43 | compileSdk = 34
44 |
45 | defaultConfig {
46 | minSdk = 24
47 | }
48 |
49 | compileOptions {
50 | sourceCompatibility = JavaVersion.VERSION_1_8
51 | targetCompatibility = JavaVersion.VERSION_1_8
52 | }
53 | }
54 |
55 | val javadocJar by tasks.creating(Jar::class) {
56 | archiveClassifier.set("javadoc")
57 | from(file("empty-javadoc"))
58 | }
59 |
60 | publishing {
61 | publications.withType().configureEach {
62 | artifact(javadocJar)
63 | pom {
64 | name.set("Delight SQL Viewer")
65 | description.set("Delight SQL Viewer is a multiplatform library that integrates database " +
66 | "viewing and editing into your application")
67 | url.set("https://github.com/bartwell/delight-sql-viewer")
68 | licenses {
69 | license {
70 | name.set("Apache License, Version 2.0")
71 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
72 | }
73 | }
74 | scm {
75 | connection.set("scm:git:git://github.com/bartwell/delight-sql-viewer.git")
76 | developerConnection.set("scm:git:ssh://github.com/bartwell/delight-sql-viewer.git")
77 | url.set("https://github.com/bartwell/delight-sql-viewer")
78 | }
79 | developers {
80 | developer {
81 | id.set("BArtWell")
82 | name.set("Artem Bazhanov")
83 | email.set("web@bartwell.ru")
84 | }
85 | }
86 | }
87 | }
88 | repositories {
89 | maven {
90 | name = "OSSRH"
91 | url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
92 | credentials {
93 | username = findProperty("ossrhUsername") as? String ?: System.getenv("OSSRH_USERNAME")
94 | password = findProperty("ossrhPassword") as? String ?: System.getenv("OSSRH_PASSWORD")
95 | }
96 | }
97 | }
98 | }
99 |
100 | signing {
101 | useInMemoryPgpKeys(
102 | findProperty("signingKeyId") as? String ?: System.getenv("SIGNING_KEY_ID"),
103 | findProperty("signingSecretKey") as? String ?: System.getenv("SIGNING_SECRET_KEY"),
104 | findProperty("signingPassword") as? String ?: System.getenv("SIGNING_PASSWORD")
105 | )
106 | sign(publishing.publications)
107 | }
108 |
109 | tasks.withType().configureEach {
110 | dependsOn(tasks.withType())
111 | }
112 |
113 | tasks.withType().configureEach {
114 | dependsOn(tasks.withType())
115 | }
116 |
--------------------------------------------------------------------------------
/stub/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/stub/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/AndroidEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import android.content.Context
4 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
5 |
6 | internal interface AndroidEnvironmentProvider : EnvironmentProvider {
7 | @Suppress("unused")
8 | fun getContext(): Context
9 | }
10 |
--------------------------------------------------------------------------------
/stub/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
4 |
5 | public abstract class RoomEnvironmentProvider : AndroidEnvironmentProvider
6 |
--------------------------------------------------------------------------------
/stub/src/androidMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import ru.bartwell.delightsqlviewer.AndroidEnvironmentProvider
4 |
5 | public abstract class SqlDelightEnvironmentProvider : AndroidEnvironmentProvider
6 |
--------------------------------------------------------------------------------
/stub/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/DelightSqlViewer.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("UnusedParameter", "EmptyFunctionBlock", "UNUSED_PARAMETER", "RedundantSuppression")
6 | public object DelightSqlViewer {
7 |
8 | public fun init(provider: EnvironmentProvider<*>) {}
9 |
10 | public fun init(provider: EnvironmentProvider<*>, isShortcutEnabled: Boolean) {}
11 |
12 | public fun launch() {}
13 | }
14 |
--------------------------------------------------------------------------------
/stub/src/commonMain/kotlin/ru/bartwell/delightsqlviewer/core/EnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.core
2 |
3 | public interface EnvironmentProvider {
4 | public fun getDriver(): T
5 | }
6 |
--------------------------------------------------------------------------------
/stub/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/ShortcutActionHandler.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer
2 |
3 | import platform.UIKit.UISceneConfiguration
4 | import platform.UIKit.UISceneSession
5 |
6 | public object ShortcutActionHandler {
7 |
8 | public fun getConfiguration(session: UISceneSession): UISceneConfiguration {
9 | return UISceneConfiguration(
10 | name = session.configuration.name,
11 | sessionRole = session.role
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/stub/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("UNUSED_PARAMETER")
6 | public class RoomEnvironmentProvider(driver: Any) : EnvironmentProvider {
7 | override fun getDriver(): Any = Unit
8 | }
9 |
--------------------------------------------------------------------------------
/stub/src/iosMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | @Suppress("UNUSED_PARAMETER")
6 | public class SqlDelightEnvironmentProvider(driver: Any) : EnvironmentProvider {
7 | override fun getDriver(): Any = Unit
8 | }
9 |
--------------------------------------------------------------------------------
/stub/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/adapter/room/RoomEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.room
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | public abstract class RoomEnvironmentProvider : EnvironmentProvider
6 |
--------------------------------------------------------------------------------
/stub/src/jvmMain/kotlin/ru/bartwell/delightsqlviewer/adapter/sqldelight/SqlDelightEnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package ru.bartwell.delightsqlviewer.adapter.sqldelight
2 |
3 | import ru.bartwell.delightsqlviewer.core.EnvironmentProvider
4 |
5 | public abstract class SqlDelightEnvironmentProvider : EnvironmentProvider
6 |
--------------------------------------------------------------------------------
/version.properties:
--------------------------------------------------------------------------------
1 | libraryVersionName=1.0.0
2 |
--------------------------------------------------------------------------------