This commit is contained in:
tapframe 2026-03-11 21:40:12 +05:30
parent 9d5d5fa4e2
commit e3d7ddb04a
3 changed files with 217 additions and 227 deletions

View file

@ -1,34 +1,29 @@
package com.nuvio.app
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Extension
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import coil3.ImageLoader
@ -95,39 +90,36 @@ fun App() {
}
NuvioTheme {
val navController = rememberNavController()
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) }
// iOS-only: StreamsScreen is presented as a native modal sheet instead of
// a NavHost destination. On Android this stays null and navController is used.
var pendingStream by remember { mutableStateOf<StreamRoute?>(null) }
val onPlay: (String, String, String, String?, String?, String?, Int?, Int?, String?, String?) -> Unit =
{ type, videoId, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail ->
val route = StreamRoute(
type = type,
videoId = videoId,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
navController.navigate(
StreamRoute(
type = type,
videoId = videoId,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
)
)
if (isIos) {
pendingStream = route
} else {
navController.navigate(route)
}
}
Scaffold(
containerColor = MaterialTheme.colorScheme.background,
contentWindowInsets = WindowInsets(0),
bottomBar = {
if (currentRoute == TabsRoute::class.qualifiedName) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
containerColor = MaterialTheme.colorScheme.background,
contentWindowInsets = WindowInsets(0),
bottomBar = {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surface,
windowInsets = WindowInsets(0),
@ -145,21 +137,24 @@ fun App() {
label = { Text("Addons") },
)
}
}
},
) { innerPadding ->
},
) { innerPadding ->
AppScreen(
tab = selectedTab,
modifier = Modifier.padding(innerPadding),
onPosterClick = { meta ->
navController.navigate(DetailRoute(type = meta.type, id = meta.id))
},
)
}
NavHost(
navController = navController,
startDestination = TabsRoute,
modifier = Modifier.fillMaxSize(),
) {
composable<TabsRoute> {
AppScreen(
tab = selectedTab,
modifier = Modifier.padding(innerPadding),
onPosterClick = { meta ->
navController.navigate(DetailRoute(type = meta.type, id = meta.id))
},
)
Unit
}
composable<DetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<DetailRoute>()
@ -171,10 +166,9 @@ fun App() {
navController.popBackStack()
},
onPlay = onPlay,
modifier = Modifier.padding(innerPadding),
modifier = Modifier.fillMaxSize(),
)
}
// Android only: iOS uses the modal sheet below instead
composable<StreamRoute> { backStackEntry ->
val route = backStackEntry.toRoute<StreamRoute>()
StreamsScreen(
@ -192,42 +186,10 @@ fun App() {
StreamsRepository.clear()
navController.popBackStack()
},
modifier = Modifier.padding(innerPadding),
modifier = Modifier.fillMaxSize(),
)
}
}
}
// iOS native modal sheet presentation for StreamsScreen
pendingStream?.let { route ->
ModalBottomSheet(
onDismissRequest = {
StreamsRepository.clear()
pendingStream = null
},
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
dragHandle = null,
contentWindowInsets = { WindowInsets.safeDrawing.only(WindowInsetsSides.Top) },
) {
StreamsScreen(
type = route.type,
videoId = route.videoId,
title = route.title,
logo = route.logo,
poster = route.poster,
background = route.background,
seasonNumber = route.seasonNumber,
episodeNumber = route.episodeNumber,
episodeTitle = route.episodeTitle,
episodeThumbnail = route.episodeThumbnail,
onBack = {
StreamsRepository.clear()
pendingStream = null
},
)
}
}
}
}

View file

@ -21,6 +21,11 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
69649F6DF5D3AF53A24CA9C3 /* Configuration */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Configuration;
sourceTree = "<group>";
};
A2F97F59D21EB4D4B662C8D1 /* iosApp */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
@ -29,11 +34,6 @@
path = iosApp;
sourceTree = "<group>";
};
69649F6DF5D3AF53A24CA9C3 /* Configuration */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Configuration;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@ -167,6 +167,120 @@
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
08C6158BC74ED5D18954BFD9 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReferenceAnchor = 69649F6DF5D3AF53A24CA9C3 /* Configuration */;
baseConfigurationReferenceRelativePath = Config.xcconfig;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
3BEA88C53B91734AEB44313E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = 8QBDZ766S3;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
4322E267F98B668D798FAEEE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = 8QBDZ766S3;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7E47803C8E15431AEC9C9A71 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReferenceAnchor = 69649F6DF5D3AF53A24CA9C3 /* Configuration */;
@ -232,120 +346,6 @@
};
name = Debug;
};
08C6158BC74ED5D18954BFD9 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReferenceAnchor = 69649F6DF5D3AF53A24CA9C3 /* Configuration */;
baseConfigurationReferenceRelativePath = Config.xcconfig;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4322E267F98B668D798FAEEE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
3BEA88C53B91734AEB44313E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -370,4 +370,4 @@
/* End XCConfigurationList section */
};
rootObject = E819B502921EC7C68AC4965A /* Project object */;
}
}

View file

@ -10,8 +10,9 @@ ANDROID_ACTIVITY=".MainActivity"
IOS_PROJECT="$ROOT_DIR/iosApp/iosApp.xcodeproj"
IOS_SCHEME="iosApp"
IOS_DERIVED_DATA="$ROOT_DIR/build/ios-derived"
IOS_APP_PATH="$IOS_DERIVED_DATA/Build/Products/Debug-iphonesimulator/Nuvio.app"
IOS_APP_NAME="Nuvio.app"
IOS_BUNDLE_ID="com.nuvio.app.Nuvio"
IOS_PREFERRED_DEVICE_MODEL="iPhone 14 Pro"
usage() {
cat <<'EOF'
@ -20,7 +21,7 @@ Usage:
./scripts/run-mobile.sh ios
Builds the debug app for the selected platform, installs it on the first running
Android emulator or booted iOS simulator, and launches the app.
Android emulator or the configured iOS device target, and launches the app.
EOF
}
@ -39,6 +40,30 @@ first_booted_ios_simulator() {
xcrun simctl list devices booted | awk -F '[()]' '/Booted/ { print $2; exit }'
}
preferred_ios_device() {
xcrun xcdevice list --timeout 5 2>/dev/null | python3 -c '
import json
import sys
import os
try:
devices = json.load(sys.stdin)
except Exception:
sys.exit(0)
physical = [
device for device in devices
if device.get("platform") == "com.apple.platform.iphoneos"
and not device.get("simulator", False)
and device.get("available") is True
and device.get("modelName") == os.environ["IOS_PREFERRED_DEVICE_MODEL"]
]
if physical:
print(physical[0].get("identifier", ""))
'
}
run_android() {
require_command adb
@ -73,35 +98,38 @@ run_ios() {
require_command xcodebuild
require_command xcrun
local simulator_udid
simulator_udid="$(first_booted_ios_simulator)"
local physical_device_id
physical_device_id="$(IOS_PREFERRED_DEVICE_MODEL="$IOS_PREFERRED_DEVICE_MODEL" preferred_ios_device)"
if [[ -z "$simulator_udid" ]]; then
echo "No booted iOS simulator found." >&2
echo "Start a simulator first, then rerun: ./scripts/run-mobile.sh ios" >&2
exit 1
if [[ -n "$physical_device_id" ]]; then
local device_app_path
device_app_path="$IOS_DERIVED_DATA/Build/Products/Debug-iphoneos/$IOS_APP_NAME"
echo "Building iOS debug app for physical device $physical_device_id..."
xcodebuild \
-project "$IOS_PROJECT" \
-scheme "$IOS_SCHEME" \
-configuration Debug \
-destination "id=$physical_device_id" \
-derivedDataPath "$IOS_DERIVED_DATA" \
build
if [[ ! -d "$device_app_path" ]]; then
echo "Expected iOS app not found at: $device_app_path" >&2
exit 1
fi
echo "Installing on physical device $physical_device_id..."
xcrun devicectl device install app --device "$physical_device_id" "$device_app_path"
echo "Launching app..."
xcrun devicectl device process launch --device "$physical_device_id" "$IOS_BUNDLE_ID"
return
fi
echo "Building iOS debug app for simulator $simulator_udid..."
xcodebuild \
-project "$IOS_PROJECT" \
-scheme "$IOS_SCHEME" \
-configuration Debug \
-destination "id=$simulator_udid" \
-derivedDataPath "$IOS_DERIVED_DATA" \
CODE_SIGNING_ALLOWED=NO \
build
if [[ ! -d "$IOS_APP_PATH" ]]; then
echo "Expected iOS app not found at: $IOS_APP_PATH" >&2
exit 1
fi
echo "Installing on simulator $simulator_udid..."
xcrun simctl install "$simulator_udid" "$IOS_APP_PATH"
echo "Launching app..."
xcrun simctl launch "$simulator_udid" "$IOS_BUNDLE_ID"
echo "Preferred iOS device not available: $IOS_PREFERRED_DEVICE_MODEL" >&2
echo "Connect and unlock that device, then rerun: ./scripts/run-mobile.sh ios" >&2
exit 1
}
main() {