Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: iOS Build

on:
workflow_dispatch:
push:
tags:
- 'v*'

jobs:
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios

- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri -> target

- run: npm ci

- run: npx tauri ios init

- name: Build unsigned iOS .app
run: |
npx tauri ios build -- \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY=""

- name: Package .ipa
run: |
APP=$(find src-tauri/gen/apple/build -name "*.app" -type d | head -1)
if [ -z "$APP" ]; then
echo "No .app found"
exit 1
fi
mkdir -p Payload
cp -r "$APP" Payload/
zip -qr nullptr-unsigned.ipa Payload
rm -rf Payload

- uses: actions/upload-artifact@v4
with:
name: nullptr-ios-unsigned
path: nullptr-unsigned.ipa

67 changes: 62 additions & 5 deletions scripts/build-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ Usage: $(basename "$0") [OPTIONS] [VERSION]
Build nullptr release artifacts for all platforms.

Options:
--desktop-only Skip Android build
--android-only Skip desktop build
--desktop-only Skip Android and iOS builds
--android-only Skip desktop and iOS builds
--ios-only Skip desktop and Android builds
--target TARGET Build only a specific Rust target (e.g. x86_64-unknown-linux-gnu)
--sign Sign Android APK (requires ANDROID_SIGNING_* env vars)
--clean Clean Rust target directory before building
Expand All @@ -43,22 +44,25 @@ Examples:
./scripts/build-release.sh # Build everything
./scripts/build-release.sh 1.0.0 # Build with version 1.0.0
./scripts/build-release.sh --desktop-only # Desktop only
./scripts/build-release.sh --ios-only # iOS only (requires macOS + Xcode)
./scripts/build-release.sh --target x86_64-unknown-linux-gnu
EOF
exit 0
}

DESKTOP=true
ANDROID=true
IOS=true
SPECIFIC_TARGET=""
SIGN_ANDROID=true
CLEAN=false
VERSION=""

while [[ $# -gt 0 ]]; do
case "$1" in
--desktop-only) ANDROID=false; shift ;;
--android-only) DESKTOP=false; shift ;;
--desktop-only) ANDROID=false; IOS=false; shift ;;
--android-only) DESKTOP=false; IOS=false; shift ;;
--ios-only) DESKTOP=false; ANDROID=false; shift ;;
--target) SPECIFIC_TARGET="$2"; shift 2 ;;
--sign) SIGN_ANDROID=true; shift ;;
--clean) CLEAN=true; shift ;;
Expand Down Expand Up @@ -229,6 +233,59 @@ if [[ "$ANDROID" == true ]]; then
echo ""
fi

# ── iOS build ──

if [[ "$IOS" == true ]]; then
log "═══ iOS Build ═══"
echo ""

if [[ "$(uname -s)" != "Darwin" ]]; then
warn "iOS builds require macOS with Xcode — skipping"
IOS=false
elif ! command -v xcodebuild &>/dev/null; then
warn "Xcode not found — skipping iOS build"
IOS=false
fi

if [[ "$IOS" == true ]]; then
if ! rustup target list --installed | grep -q "aarch64-apple-ios"; then
rustup target add aarch64-apple-ios
fi

if [[ ! -d "src-tauri/gen/apple" ]]; then
log "Initializing Tauri iOS project..."
npx tauri ios init
fi

log "Building unsigned iOS .app (development export)..."
npx tauri ios build -- -allowProvisioningUpdates \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY="" \
2>&1 | tail -10

APP_DIR="src-tauri/gen/apple/build"
if [[ -d "$APP_DIR" ]]; then
mkdir -p "$OUTPUT_DIR/ios"
find "$APP_DIR" -name "*.app" -type d | while read -r app; do
app_name="$(basename "$app")"
cp -r "$app" "$OUTPUT_DIR/ios/$app_name"

# Package into an unsigned .ipa for distribution
PAYLOAD_DIR=$(mktemp -d)
mkdir -p "$PAYLOAD_DIR/Payload"
cp -r "$app" "$PAYLOAD_DIR/Payload/"
(cd "$PAYLOAD_DIR" && zip -qr "${OLDPWD}/${OUTPUT_DIR}/ios/nullptr-${VERSION}-unsigned.ipa" Payload)
rm -rf "$PAYLOAD_DIR"
done
ok "iOS .app → ${OUTPUT_DIR}/ios/"
else
warn "No iOS build output found"
fi
fi
echo ""
fi

# ── Summary ──

echo ""
Expand All @@ -241,7 +298,7 @@ echo ""
if [[ -d "$OUTPUT_DIR" ]]; then
find "$OUTPUT_DIR" -type f \( -name "*.deb" -o -name "*.AppImage" -o -name "*.rpm" \
-o -name "*.dmg" -o -name "*.app" -o -name "*.msi" -o -name "*.exe" \
-o -name "*.apk" \) -exec sh -c '
-o -name "*.apk" -o -name "*.ipa" \) -exec sh -c '
for f; do
size=$(du -h "$f" | cut -f1)
echo " ${size} $(basename "$f")"
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"appimage": {
"bundleMediaFramework": false
}
},
"iOS": {
"developmentTeam": ""
}
}
}
8 changes: 8 additions & 0 deletions src/app/features/room/Room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import { useRoomMembers } from '../../hooks/useRoomMembers';
import { CallView } from '../call/CallView';
import { RoomViewHeader } from './RoomViewHeader';
import { useCallState } from '../../pages/client/call/CallProvider';
import { WidgetsDrawer } from '../widgets/WidgetsDrawer';

export function Room() {
const { eventId } = useParams();
const room = useRoom();
const mx = useMatrixClient();

const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const [isWidgetDrawerOpen] = useSetting(settingsAtom, 'isWidgetDrawer');
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const screenSize = useScreenSizeContext();
const powerLevels = usePowerLevels(room);
Expand Down Expand Up @@ -60,6 +62,12 @@ export function Room() {
<MembersDrawer key={room.roomId} room={room} members={members} />
</>
)}
{screenSize === ScreenSize.Desktop && isWidgetDrawerOpen && (
<>
<Line variant="Background" direction="Vertical" size="300" />
<WidgetsDrawer key={`widgets-${room.roomId}`} room={room} />
</>
)}
</Box>
</PowerLevelsContextProvider>
);
Expand Down
43 changes: 43 additions & 0 deletions src/app/features/room/RoomViewHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { useCallState } from '../../pages/client/call/CallProvider';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { useRoomWidgets } from '../../hooks/useRoomWidgets';

type RoomMenuProps = {
room: Room;
Expand Down Expand Up @@ -277,6 +278,8 @@ export function RoomViewHeader() {
: undefined;

const [peopleDrawer, setPeopleDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const [widgetDrawer, setWidgetDrawer] = useSetting(settingsAtom, 'isWidgetDrawer');
const widgets = useRoomWidgets(room);

const handleSearchClick = () => {
const searchParams: _SearchPathSearchParams = {
Expand Down Expand Up @@ -450,6 +453,46 @@ export function RoomViewHeader() {
</>
)}

{screenSize === ScreenSize.Desktop && (
<TooltipProvider
position="Bottom"
offset={4}
tooltip={
<Tooltip>
<Text>{widgetDrawer ? 'Hide Widgets' : 'Show Widgets'}</Text>
</Tooltip>
}
>
{(triggerRef) => (
<IconButton
fill="None"
ref={triggerRef}
onClick={() => setWidgetDrawer((d) => !d)}
style={{ position: 'relative' }}
>
{widgets.length > 0 && (
<Badge
style={{
position: 'absolute',
left: toRem(3),
top: toRem(3),
}}
variant="Secondary"
size="400"
fill="Solid"
radii="Pill"
>
<Text as="span" size="L400">
{widgets.length}
</Text>
</Badge>
)}
<Icon size="400" src={Icons.Category} filled={widgetDrawer} />
</IconButton>
)}
</TooltipProvider>
)}

{screenSize === ScreenSize.Desktop && (
<TooltipProvider
position="Bottom"
Expand Down
Loading