commit ea0bf38748c9ee3cb79d6494ff52015129539483
parent 41e4af5e47028a3544d69dbaea5ebaa276666daa
Author: Jean Schurger <jean@schurger.org>
Date: Wed, 1 Apr 2026 09:23:57 -0400
Add end-of-track detection, Ko-fi gating, zoom, waveform res, and Fastlane
- Playback stops at end of song instead of continuing
- Ko-fi button only appears after 5+ plays (NSUserDefaults counter)
- Pinch-to-zoom on waveform timeline with auto-scroll
- Waveform resolution at 1 sample per 20ms (1/50th second)
- Time label with background for contrast on played sections
- Fix first bar highlight bug
- Fastlane setup for App Store release via ASC API key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
12 files changed, 245 insertions(+), 104 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -8,3 +8,8 @@ iosApp/*.xcworkspace
iosApp/*.xcodeproj/xcuserdata
iosApp/Pods/
*.xcuserstate
+iosApp/fastlane/report.xml
+iosApp/fastlane/README.md
+iosApp/fastlane/test_output/
+*.ipa
+*.dSYM.zip
diff --git a/composeApp/src/androidMain/kotlin/ubi2/sonodrill/model/android_platform.kt b/composeApp/src/androidMain/kotlin/ubi2/sonodrill/model/android_platform.kt
@@ -3,6 +3,6 @@ package ubi2.sonodrill.model
actual fun current_time_ms(): Long = System.currentTimeMillis()
actual fun generate_uuid(): String = java.util.UUID.randomUUID().toString()
actual fun file_exists(path: String): Boolean = java.io.File(path).exists()
-actual fun open_url(url: String) {
- // No-op: needs Activity context, will implement if needed
-}
+actual fun open_url(url: String) {}
+actual fun get_play_count(): Int = 0
+actual fun increment_play_count() {}
diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/model/platform.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/model/platform.kt
@@ -4,3 +4,5 @@ expect fun current_time_ms(): Long
expect fun generate_uuid(): String
expect fun file_exists(path: String): Boolean
expect fun open_url(url: String)
+expect fun get_play_count(): Int
+expect fun increment_play_count()
diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/app.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/app.kt
@@ -9,6 +9,7 @@ import ubi2.sonodrill.audio.AudioPlayer
import ubi2.sonodrill.data.AudioRepository
import ubi2.sonodrill.model.AudioFileInfo
import ubi2.sonodrill.model.file_exists
+import ubi2.sonodrill.model.get_play_count
import ubi2.sonodrill.ui.library.LibraryScreen
import ubi2.sonodrill.ui.player.PlayerScreen
import ubi2.sonodrill.ui.player.PlayerViewModel
@@ -49,6 +50,7 @@ fun SonodrillApp(player: AudioPlayer, repo: AudioRepository, on_import: (on_done
},
on_import = { on_import { refresh_files() } },
on_delete = { id -> repo.delete_file(id); refresh_files() },
+ show_kofi = get_play_count() > 5,
)
Screen.PLAYER -> player_vm?.let { vm ->
PlayerScreen(vm = vm, on_back = { vm.release(); player_vm = null; refresh_files(); screen = Screen.LIBRARY })
diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/library/library_screen.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/library/library_screen.kt
@@ -26,6 +26,7 @@ fun LibraryScreen(
on_file_click: (AudioFileInfo) -> Unit,
on_import: () -> Unit,
on_delete: (String) -> Unit,
+ show_kofi: Boolean = false,
) {
var search by remember { mutableStateOf("") }
val filtered = if (search.isBlank()) files else files.filter { it.title.contains(search, ignoreCase = true) }
@@ -35,8 +36,10 @@ fun LibraryScreen(
TopAppBar(
title = { Text("Sonodrill") },
actions = {
- IconButton(onClick = { open_url("https://ko-fi.com/jeanschurger") }) {
- Icon(Icons.Default.Favorite, "Support on Ko-fi", tint = MaterialTheme.colorScheme.error)
+ if (show_kofi) {
+ IconButton(onClick = { open_url("https://ko-fi.com/jeanschurger") }) {
+ Icon(Icons.Default.Favorite, "Support on Ko-fi", tint = MaterialTheme.colorScheme.error)
+ }
}
},
)
diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_screen.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_screen.kt
@@ -3,7 +3,10 @@ package ubi2.sonodrill.ui.player
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
@@ -178,6 +181,9 @@ fun PlayerScreen(vm: PlayerViewModel, on_back: () -> Unit) {
}
}
+private const val BASE_ROWS = 6
+private const val ROW_HEIGHT_DP = 56
+
@Composable
private fun ProgressTimeline(
position_ms: Long,
@@ -193,109 +199,174 @@ private fun ProgressTimeline(
val mark_color = Color(0xFFFF6B35)
val loop_color = MaterialTheme.colorScheme.primaryContainer
- val rows = 6
+ var zoom by remember { mutableFloatStateOf(1f) }
+ val rows = (BASE_ROWS * zoom).toInt().coerceAtLeast(BASE_ROWS)
val row_duration = if (duration_ms > 0) duration_ms.toFloat() / rows else 1f
val samples_per_row = if (waveform.isNotEmpty()) waveform.size.toFloat() / rows else 0f
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.spacedBy(4.dp),
- ) {
- for (row in 0 until rows) {
- val row_start_ms = row * row_duration
- val row_end_ms = (row + 1) * row_duration
- val row_progress = when {
- position_ms >= row_end_ms -> 1f
- position_ms <= row_start_ms -> 0f
- else -> (position_ms - row_start_ms) / (row_end_ms - row_start_ms)
+ val list_state = rememberLazyListState()
+ val is_zoomed = rows > BASE_ROWS
+
+ // Auto-scroll to keep playhead visible
+ val active_row = if (duration_ms > 0) ((position_ms.toFloat() / duration_ms) * rows).toInt().coerceIn(0, rows - 1) else 0
+ LaunchedEffect(active_row, rows) {
+ if (is_zoomed) {
+ val visible = list_state.layoutInfo.visibleItemsInfo
+ val first_visible = visible.firstOrNull()?.index ?: 0
+ val last_visible = visible.lastOrNull()?.index ?: 0
+ if (active_row < first_visible || active_row > last_visible) {
+ list_state.animateScrollToItem((active_row - 2).coerceAtLeast(0))
}
- val row_marks = marks.filter { it.position_ms >= row_start_ms && it.position_ms < row_end_ms }
- val is_active_row = position_ms >= row_start_ms && position_ms < row_end_ms
- val wave_start = (row * samples_per_row).toInt()
- val wave_end = ((row + 1) * samples_per_row).toInt().coerceAtMost(waveform.size)
+ }
+ }
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .clip(RoundedCornerShape(6.dp))
- .background(surface_variant)
- .pointerInput(row, duration_ms) {
- detectTapGestures { offset ->
- val fraction = (offset.x / size.width).coerceIn(0f, 1f)
- val tap_ms = (row_start_ms + fraction * (row_end_ms - row_start_ms)).toLong()
- on_seek(tap_ms)
- }
- }
+ Box(
+ modifier = Modifier.fillMaxSize().pointerInput(Unit) {
+ detectTransformGestures { _, _, gestureZoom, _ ->
+ zoom = (zoom * gestureZoom).coerceIn(1f, 20f)
+ }
+ }
+ ) {
+ if (is_zoomed) {
+ // Scrollable when zoomed
+ LazyColumn(
+ state = list_state,
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
) {
- Canvas(modifier = Modifier.fillMaxSize()) {
- val w = size.width
- val h = size.height
- val mid_y = h / 2f
-
- // Loop region highlight
- if (loop_state.enabled && loop_state.mark_a != null && loop_state.mark_b != null) {
- val a = loop_state.mark_a!!.position_ms.toFloat()
- val b = loop_state.mark_b!!.position_ms.toFloat()
- val ls = ((a - row_start_ms) / (row_end_ms - row_start_ms)).coerceIn(0f, 1f)
- val le = ((b - row_start_ms) / (row_end_ms - row_start_ms)).coerceIn(0f, 1f)
- if (le > ls && a < row_end_ms && b > row_start_ms) {
- drawRect(
- color = loop_color,
- topLeft = Offset(ls * w, 0f),
- size = androidx.compose.ui.geometry.Size((le - ls) * w, h),
- )
- }
- }
+ items(rows) { row ->
+ TimelineRow(
+ row = row, row_duration = row_duration, samples_per_row = samples_per_row,
+ position_ms = position_ms, duration_ms = duration_ms, marks = marks,
+ loop_state = loop_state, waveform = waveform, on_seek = on_seek,
+ primary = primary, surface_variant = surface_variant, on_surface = on_surface,
+ mark_color = mark_color, loop_color = loop_color,
+ modifier = Modifier.fillMaxWidth().height(ROW_HEIGHT_DP.dp),
+ )
+ }
+ }
+ } else {
+ // Fixed layout when not zoomed
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ for (row in 0 until rows) {
+ TimelineRow(
+ row = row, row_duration = row_duration, samples_per_row = samples_per_row,
+ position_ms = position_ms, duration_ms = duration_ms, marks = marks,
+ loop_state = loop_state, waveform = waveform, on_seek = on_seek,
+ primary = primary, surface_variant = surface_variant, on_surface = on_surface,
+ mark_color = mark_color, loop_color = loop_color,
+ modifier = Modifier.fillMaxWidth().weight(1f),
+ )
+ }
+ }
+ }
+ }
+}
- // Waveform or simple progress
- val row_samples = wave_end - wave_start
- if (row_samples > 0) {
- val bar_width = w / row_samples
- for (i in 0 until row_samples) {
- val amp = waveform[wave_start + i].coerceIn(0f, 1f)
- val bar_h = amp * h * 0.9f
- val x = i * bar_width
- val frac = i.toFloat() / row_samples
- val color = if (frac <= row_progress) primary else primary.copy(alpha = 0.3f)
- drawRect(
- color = color,
- topLeft = Offset(x, mid_y - bar_h / 2f),
- size = androidx.compose.ui.geometry.Size(bar_width.coerceAtLeast(1f), bar_h.coerceAtLeast(1f)),
- )
- }
- } else if (row_progress > 0f) {
- drawRect(
- color = primary.copy(alpha = 0.3f),
- topLeft = Offset.Zero,
- size = androidx.compose.ui.geometry.Size(row_progress * w, h),
- )
- }
+@Composable
+private fun TimelineRow(
+ row: Int, row_duration: Float, samples_per_row: Float,
+ position_ms: Long, duration_ms: Long, marks: List<MarkInfo>,
+ loop_state: LoopState, waveform: FloatArray, on_seek: (Long) -> Unit,
+ primary: Color, surface_variant: Color, on_surface: Color,
+ mark_color: Color, loop_color: Color,
+ modifier: Modifier,
+) {
+ val row_start_ms = row * row_duration
+ val row_end_ms = (row + 1) * row_duration
+ val row_progress = when {
+ position_ms >= row_end_ms -> 1f
+ position_ms <= row_start_ms -> 0f
+ else -> (position_ms - row_start_ms) / (row_end_ms - row_start_ms)
+ }
+ val row_marks = marks.filter { it.position_ms >= row_start_ms && it.position_ms < row_end_ms }
+ val is_active_row = position_ms >= row_start_ms && position_ms < row_end_ms
+ val wave_start = (row * samples_per_row).toInt()
+ val wave_end = ((row + 1) * samples_per_row).toInt().coerceAtMost(waveform.size)
- // Marks as vertical lines
- for (mark in row_marks) {
- val mark_frac = (mark.position_ms - row_start_ms) / (row_end_ms - row_start_ms)
- val x = mark_frac * w
- drawLine(color = mark_color, start = Offset(x, 0f), end = Offset(x, h), strokeWidth = 3f)
- drawCircle(color = mark_color, radius = 5f, center = Offset(x, 5f))
- }
+ Box(
+ modifier = modifier
+ .clip(RoundedCornerShape(6.dp))
+ .background(surface_variant)
+ .pointerInput(row, duration_ms) {
+ detectTapGestures { offset ->
+ val fraction = (offset.x / size.width).coerceIn(0f, 1f)
+ val tap_ms = (row_start_ms + fraction * (row_end_ms - row_start_ms)).toLong()
+ on_seek(tap_ms)
+ }
+ }
+ ) {
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ val w = size.width
+ val h = size.height
+ val mid_y = h / 2f
- // Playhead cursor
- if (is_active_row) {
- val cursor_x = row_progress * w
- drawLine(color = on_surface, start = Offset(cursor_x, 0f), end = Offset(cursor_x, h), strokeWidth = 2.5f)
- }
+ // Loop region highlight
+ if (loop_state.enabled && loop_state.mark_a != null && loop_state.mark_b != null) {
+ val a = loop_state.mark_a!!.position_ms.toFloat()
+ val b = loop_state.mark_b!!.position_ms.toFloat()
+ val ls = ((a - row_start_ms) / (row_end_ms - row_start_ms)).coerceIn(0f, 1f)
+ val le = ((b - row_start_ms) / (row_end_ms - row_start_ms)).coerceIn(0f, 1f)
+ if (le > ls && a < row_end_ms && b > row_start_ms) {
+ drawRect(
+ color = loop_color,
+ topLeft = Offset(ls * w, 0f),
+ size = androidx.compose.ui.geometry.Size((le - ls) * w, h),
+ )
}
+ }
- // Row time label
- Text(
- text = format_time(row_start_ms.toLong()),
- fontSize = 10.sp,
- color = on_surface.copy(alpha = 0.5f),
- modifier = Modifier.padding(start = 4.dp, top = 2.dp),
+ // Waveform or simple progress
+ val row_samples = wave_end - wave_start
+ if (row_samples > 0) {
+ val bar_width = w / row_samples
+ for (i in 0 until row_samples) {
+ val amp = waveform[wave_start + i].coerceIn(0f, 1f)
+ val bar_h = amp * h * 0.9f
+ val x = i * bar_width
+ val frac = (i + 1).toFloat() / row_samples
+ val color = if (frac <= row_progress) primary else primary.copy(alpha = 0.3f)
+ drawRect(
+ color = color,
+ topLeft = Offset(x, mid_y - bar_h / 2f),
+ size = androidx.compose.ui.geometry.Size(bar_width.coerceAtLeast(1f), bar_h.coerceAtLeast(1f)),
+ )
+ }
+ } else if (row_progress > 0f) {
+ drawRect(
+ color = primary.copy(alpha = 0.3f),
+ topLeft = Offset.Zero,
+ size = androidx.compose.ui.geometry.Size(row_progress * w, h),
)
}
+
+ // Marks as vertical lines
+ for (mark in row_marks) {
+ val mark_frac = (mark.position_ms - row_start_ms) / (row_end_ms - row_start_ms)
+ val x = mark_frac * w
+ drawLine(color = mark_color, start = Offset(x, 0f), end = Offset(x, h), strokeWidth = 3f)
+ drawCircle(color = mark_color, radius = 5f, center = Offset(x, 5f))
+ }
+
+ // Playhead cursor
+ if (is_active_row) {
+ val cursor_x = row_progress * w
+ drawLine(color = on_surface, start = Offset(cursor_x, 0f), end = Offset(cursor_x, h), strokeWidth = 2.5f)
+ }
}
+
+ // Row time label with background for contrast
+ Text(
+ text = format_time(row_start_ms.toLong()),
+ fontSize = 10.sp,
+ color = on_surface,
+ modifier = Modifier.padding(start = 2.dp, top = 1.dp)
+ .background(surface_variant.copy(alpha = 0.7f), RoundedCornerShape(3.dp))
+ .padding(horizontal = 3.dp, vertical = 1.dp),
+ )
}
}
diff --git a/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_view_model.kt b/composeApp/src/commonMain/kotlin/ubi2/sonodrill/ui/player/player_view_model.kt
@@ -9,6 +9,7 @@ import ubi2.sonodrill.model.LoopState
import ubi2.sonodrill.model.MarkInfo
import ubi2.sonodrill.model.current_time_ms
import ubi2.sonodrill.model.generate_uuid
+import ubi2.sonodrill.model.increment_play_count
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@@ -38,12 +39,13 @@ class PlayerViewModel(
fun load_file(file: AudioFileInfo) {
_current_file.value = file
player.load(file.uri)
+ increment_play_count()
_marks.value = repo.get_marks(file.id)
if (file.last_position_ms > 0) player.seek(file.last_position_ms)
if (file.last_speed != 1f) player.set_speed(file.last_speed)
scope.launch {
val dur = duration.first { it > 0 }
- _waveform.value = extract_waveform(file.uri, (dur / 100).toInt().coerceAtLeast(1))
+ _waveform.value = extract_waveform(file.uri, (dur / 20).toInt().coerceAtLeast(1))
}
start_auto_save()
}
diff --git a/composeApp/src/iosMain/kotlin/ubi2/sonodrill/audio/ios_audio_player.kt b/composeApp/src/iosMain/kotlin/ubi2/sonodrill/audio/ios_audio_player.kt
@@ -96,7 +96,14 @@ class IosAudioPlayer : AudioPlayer {
val node = player_node ?: return; val file = audio_file ?: return
val time = node.lastRenderTime ?: return; val pt = node.playerTimeForNodeTime(time) ?: return
val elapsed_ms = ((pt.sampleTime.toDouble() / file.processingFormat.sampleRate) * 1000).toLong()
- _position.value = seek_offset_ms + elapsed_ms
+ val pos = seek_offset_ms + elapsed_ms
+ if (pos >= _duration.value) {
+ _position.value = _duration.value
+ _playing.value = false
+ stop_position_updates()
+ return
+ }
+ _position.value = pos
}
}
diff --git a/composeApp/src/iosMain/kotlin/ubi2/sonodrill/model/ios_platform.kt b/composeApp/src/iosMain/kotlin/ubi2/sonodrill/model/ios_platform.kt
@@ -11,3 +11,11 @@ actual fun open_url(url: String) {
val nsurl = platform.Foundation.NSURL.URLWithString(url) ?: return
platform.UIKit.UIApplication.sharedApplication.openURL(nsurl, options = emptyMap<Any?, Any>(), completionHandler = null)
}
+
+private const val PLAY_COUNT_KEY = "sonodrill_play_count"
+actual fun get_play_count(): Int =
+ platform.Foundation.NSUserDefaults.standardUserDefaults.integerForKey(PLAY_COUNT_KEY).toInt()
+actual fun increment_play_count() {
+ val defaults = platform.Foundation.NSUserDefaults.standardUserDefaults
+ defaults.setInteger((defaults.integerForKey(PLAY_COUNT_KEY) + 1), PLAY_COUNT_KEY)
+}
diff --git a/iosApp/fastlane/Appfile b/iosApp/fastlane/Appfile
@@ -0,0 +1,3 @@
+app_identifier("ubi2.sonodrill")
+apple_id(ENV["UBI2_APPLE_ID"])
+team_id("8CH9CCT4MH")
diff --git a/iosApp/fastlane/Fastfile b/iosApp/fastlane/Fastfile
@@ -0,0 +1,41 @@
+default_platform(:ios)
+
+platform :ios do
+ desc "Build Kotlin framework and archive for App Store"
+ lane :release do
+ # Build Kotlin framework first
+ Dir.chdir("..") do
+ sh("cd .. && ./gradlew :composeApp:assembleReleaseAppleFrameworkForXcode " \
+ "-PXCODE_CONFIGURATION=Release " \
+ "-PXCODE_SDK=iphoneos " \
+ "-PXCODE_ARCHS=arm64")
+ end
+
+ # Regenerate Xcode project
+ sh("cd .. && xcodegen generate")
+
+ api_key = app_store_connect_api_key(
+ key_id: ENV["UBI2_ASC_KEY_ID"],
+ issuer_id: ENV["UBI2_ASC_ISSUER_ID"],
+ key_filepath: ENV["UBI2_ASC_KEY_PATH"],
+ )
+
+ increment_build_number(
+ xcodeproj: "iosApp.xcodeproj",
+ build_number: Time.now.strftime("%Y%m%d%H%M"),
+ )
+
+ build_app(
+ project: "iosApp.xcodeproj",
+ scheme: "iosApp",
+ export_method: "app-store",
+ destination: "generic/platform=iOS",
+ xcargs: "-allowProvisioningUpdates",
+ )
+
+ upload_to_testflight(
+ api_key: api_key,
+ skip_waiting_for_build_processing: true,
+ )
+ end
+end
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -19,7 +19,7 @@
245434685F34A9A15B1AC406 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
2BCF6AAC471512DFD0FA06EE /* WaveformExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformExtractor.swift; sourceTree = "<group>"; };
8395CD5B070D5A2FB485F0DA /* iosApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = iosApp.xcodeproj; sourceTree = "<group>"; };
- 86038AE1032E284E79B32AD6 /* iosApp.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; name = iosApp.app; path = Sonodrill.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 86038AE1032E284E79B32AD6 /* Sonodrill.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sonodrill.app; sourceTree = BUILT_PRODUCTS_DIR; };
CE8509E5A49E4A5CAAEB3ADB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
E74C38E65621F5192E1B68D2 /* SonodrillApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SonodrillApp.swift; sourceTree = "<group>"; };
F543015C899423D4B01CEB2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -75,7 +75,7 @@
94C98B8B3A2BFC1966447269 /* Products */ = {
isa = PBXGroup;
children = (
- 86038AE1032E284E79B32AD6 /* iosApp.app */,
+ 86038AE1032E284E79B32AD6 /* Sonodrill.app */,
);
name = Products;
sourceTree = "<group>";
@@ -100,7 +100,7 @@
packageProductDependencies = (
);
productName = iosApp;
- productReference = 86038AE1032E284E79B32AD6 /* iosApp.app */;
+ productReference = 86038AE1032E284E79B32AD6 /* Sonodrill.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -111,11 +111,6 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
- TargetAttributes = {
- 78B53CC79860CBFC2F5543C6 = {
- DevelopmentTeam = "";
- };
- };
};
buildConfigurationList = FF7CA1CA754D017066F1822F /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 14.0";
@@ -315,6 +310,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
+ DEVELOPMENT_TEAM = 8CH9CCT4MH;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -334,6 +330,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
+ DEVELOPMENT_TEAM = 8CH9CCT4MH;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = (