diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..f9da12e --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +6.3.2 \ No newline at end of file diff --git a/Demo/app/build.gradle.kts b/Demo/app/build.gradle.kts index 47e3b52..de2206e 100644 --- a/Demo/app/build.gradle.kts +++ b/Demo/app/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.foundation) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/Demo/app/src/main/java/com/pureswift/swiftandroid/ComposeListView.kt b/Demo/app/src/main/java/com/pureswift/swiftandroid/ComposeListView.kt new file mode 100644 index 0000000..ea8fb19 --- /dev/null +++ b/Demo/app/src/main/java/com/pureswift/swiftandroid/ComposeListView.kt @@ -0,0 +1,48 @@ +package com.pureswift.swiftandroid + +import android.content.Context +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import com.pureswift.swiftandroid.ui.theme.SwiftAndroidTheme + +// SwiftUI `List` backed by a Jetpack Compose `LazyColumn`, sourcing its rows from a Swift-implemented `ListViewAdapter`. +// `ComposeView` is final, so it's hosted as a child of this `FrameLayout` rather than subclassed. +class ComposeListView(context: Context, private val adapter: ListViewAdapter) : FrameLayout(context) { + + private var version by mutableIntStateOf(0) + + init { + val composeView = ComposeView(context) + composeView.setContent { + SwiftAndroidTheme { + key(version) { + LazyColumn { + items(adapter.getCount()) { index -> + Text( + text = adapter.getItem(index) as String, + modifier = Modifier.padding(16.dp) + ) + } + } + } + } + } + addView(composeView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) + } + + fun getAdapter(): ListViewAdapter = adapter + + fun refresh() { + version++ + } +} diff --git a/Demo/gradle/libs.versions.toml b/Demo/gradle/libs.versions.toml index c9e697e..bc372e3 100644 --- a/Demo/gradle/libs.versions.toml +++ b/Demo/gradle/libs.versions.toml @@ -24,6 +24,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/Sources/AndroidSwiftUI/AndroidListView.swift b/Sources/AndroidSwiftUI/AndroidListView.swift index 575c2fc..15d088f 100644 --- a/Sources/AndroidSwiftUI/AndroidListView.swift +++ b/Sources/AndroidSwiftUI/AndroidListView.swift @@ -7,7 +7,7 @@ import AndroidKit -/// SwitUI View for Android `android.widget.ListView` +/// SwiftUI `List` for Android, backed by a Jetpack Compose `LazyColumn`. public struct AndroidListView { let items: [String] @@ -18,47 +18,20 @@ public struct AndroidListView { } extension AndroidListView: AndroidViewRepresentable { - + /// Creates the view object and configures its initial state. - public func makeAndroidView(context: Self.Context) -> ListView { - createView(context: context.androidContext) - } - - /// Updates the state of the specified view with new information from SwiftUI. - public func updateAndroidView(_ view: ListView, context: Self.Context) { - updateView(view) + public func makeAndroidView(context: Self.Context) -> ComposeListView { + let adapter = ListViewAdapter(swiftObject: SwiftObject(ListViewAdapter.Context(items: items))) + return ComposeListView(context.androidContext, adapter) } -} -extension AndroidListView { - - func createView(context: AndroidContent.Context) -> AndroidWidget.ListView { - let view = AndroidWidget.ListView(context) - updateView(view) - return view - } - - func updateView(_ view: AndroidWidget.ListView) { - let layout = try! JavaClass() - let resource = layout.simple_list_item_1 - let objects: [JavaObject?] = items.map { JavaString($0) } - let adapter = ArrayAdapter( - context: view.getContext(), - resource: resource, - objects: objects - ) - view.setAdapter(adapter.as(Adapter.self)) + /// Updates the state of the specified view with new information from SwiftUI. + public func updateAndroidView(_ view: ComposeListView, context: Self.Context) { + guard let adapter = view.getAdapter() else { + assertionFailure("Missing adapter") + return + } + adapter.context = ListViewAdapter.Context(items: items) + view.refresh() } } - -extension JavaClass { - - @JavaStaticField(isFinal: true) - public var list_view_row: Int32 -} - -extension JavaClass { - - @JavaStaticField(isFinal: true) - public var textView: Int32 -} diff --git a/Sources/AndroidSwiftUI/AndroidRenderer.swift b/Sources/AndroidSwiftUI/AndroidRenderer.swift index 3824e52..04dc270 100644 --- a/Sources/AndroidSwiftUI/AndroidRenderer.swift +++ b/Sources/AndroidSwiftUI/AndroidRenderer.swift @@ -5,7 +5,6 @@ // Created by Alsey Coleman Miller on 6/8/25. // -import JavaKit import AndroidKit final class AndroidRenderer: Renderer { @@ -74,6 +73,10 @@ final class AndroidRenderer: Renderer { viewGroup.addView(viewObject) log("\(self).\(#function) \(#line): Add \(viewObject.getClass().getName()) to \(viewGroup.getClass().getName())") return AndroidTarget(host.view, viewObject) + case .fragment: + // TODO: Mount into a fragment via FragmentManager/FragmentTransaction + log("\(self).\(#function) \(#line) Fragment mounting not yet implemented") + return nil } } else { @@ -106,6 +109,9 @@ final class AndroidRenderer: Renderer { case .view(let view): log("\(self).\(#function) Update \(view.getClass().getName())") widget.updateAndroidView(view) + case .fragment: + // TODO: Update fragment + break } } diff --git a/Sources/AndroidSwiftUI/AndroidTarget.swift b/Sources/AndroidSwiftUI/AndroidTarget.swift index d474128..ec97b0f 100644 --- a/Sources/AndroidSwiftUI/AndroidTarget.swift +++ b/Sources/AndroidSwiftUI/AndroidTarget.swift @@ -5,7 +5,6 @@ // Created by Alsey Coleman Miller on 6/8/25. // -import JavaKit import AndroidKit final class AndroidTarget: Target { diff --git a/Sources/AndroidSwiftUI/ComposeListView.swift b/Sources/AndroidSwiftUI/ComposeListView.swift new file mode 100644 index 0000000..7a862a8 --- /dev/null +++ b/Sources/AndroidSwiftUI/ComposeListView.swift @@ -0,0 +1,22 @@ +// +// ComposeListView.swift +// AndroidSwiftUI +// +// Created by Alsey Coleman Miller on 7/16/26. +// + +import AndroidKit + +/// SwiftUI `List` backed by a Jetpack Compose `LazyColumn`. +@JavaClass("com.pureswift.swiftandroid.ComposeListView") +open class ComposeListView: AndroidView.View { + + @JavaMethod + @_nonoverride public convenience init(_ context: AndroidContent.Context?, _ adapter: ListViewAdapter?, environment: JNIEnvironment? = nil) + + @JavaMethod + open func getAdapter() -> ListViewAdapter? + + @JavaMethod + open func refresh() +} diff --git a/Sources/AndroidSwiftUI/Fragment.swift b/Sources/AndroidSwiftUI/Fragment.swift index a058472..c207f5f 100644 --- a/Sources/AndroidSwiftUI/Fragment.swift +++ b/Sources/AndroidSwiftUI/Fragment.swift @@ -58,28 +58,27 @@ public extension Fragment { } extension Fragment { - - static var logTag: LogTag { "Fragment" } - + + static var logTag: String { "Fragment" } + + static let log = try! JavaClass() + static func log(_ string: String) { - try? AndroidLogger(tag: logTag, priority: .debug) - .log(string) + _ = Self.log.d(Self.logTag, string) } - + static func logInfo(_ string: String) { - try? AndroidLogger(tag: logTag, priority: .info) - .log(string) + _ = Self.log.i(Self.logTag, string) } - + static func logError(_ string: String) { - try? AndroidLogger(tag: logTag, priority: .error) - .log(string) + _ = Self.log.e(Self.logTag, string) } - + func log(_ string: String) { Self.log(string) } - + func logError(_ string: String) { Self.logError(string) } diff --git a/Sources/AndroidSwiftUI/ImageView.swift b/Sources/AndroidSwiftUI/ImageView.swift index 47fc26c..5a62452 100644 --- a/Sources/AndroidSwiftUI/ImageView.swift +++ b/Sources/AndroidSwiftUI/ImageView.swift @@ -5,6 +5,7 @@ // Created by Alsey Coleman Miller on 6/9/25. // +import Foundation import AndroidKit extension Image: AnyAndroidView { diff --git a/Sources/AndroidSwiftUI/JavaRetainedValue.swift b/Sources/AndroidSwiftUI/JavaRetainedValue.swift index 8840eb5..e12a9c7 100644 --- a/Sources/AndroidSwiftUI/JavaRetainedValue.swift +++ b/Sources/AndroidSwiftUI/JavaRetainedValue.swift @@ -5,8 +5,7 @@ // Created by Alsey Coleman Miller on 6/9/25. // -import JavaKit -import JavaRuntime +import SwiftJava /// Java class that retains a Swift value for the duration of its lifetime. @JavaClass("com.pureswift.swiftandroid.SwiftObject") diff --git a/Sources/AndroidSwiftUI/ListViewAdapter.swift b/Sources/AndroidSwiftUI/ListViewAdapter.swift index 6fff3c9..977b566 100644 --- a/Sources/AndroidSwiftUI/ListViewAdapter.swift +++ b/Sources/AndroidSwiftUI/ListViewAdapter.swift @@ -8,7 +8,7 @@ import Foundation import AndroidKit -@JavaClass("com.pureswift.swiftandroid.ListViewAdapter", extends: ListAdapter.self) +@JavaClass("com.pureswift.swiftandroid.ListViewAdapter", extends: AndroidWidget.ListAdapter.self) open class ListViewAdapter: JavaObject { @JavaMethod diff --git a/Sources/AndroidSwiftUI/Runnable.swift b/Sources/AndroidSwiftUI/Runnable.swift index bb8976a..f0cbb22 100644 --- a/Sources/AndroidSwiftUI/Runnable.swift +++ b/Sources/AndroidSwiftUI/Runnable.swift @@ -5,8 +5,6 @@ // Created by Alsey Coleman Miller on 6/9/25. // -import JavaKit -import JavaRuntime import AndroidKit import JavaLang /*