From 5e4c421b96dd70d252b1def74cc76270bc264c54 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 09:59:01 -0500 Subject: [PATCH 1/7] Sync Capacitor Android bindings for calendar plugin The @ebarooni/capacitor-calendar plugin was added in #200 but the Android native bindings were never synced. Catch up via `npx cap sync android` so the plugin registers on Android builds. --- android/app/capacitor.build.gradle | 1 + android/app/src/main/assets/capacitor.plugins.json | 4 ++++ android/capacitor.settings.gradle | 3 +++ 3 files changed, 8 insertions(+) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 23a34e87..f0296a8e 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -22,6 +22,7 @@ dependencies { implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') implementation project(':capacitor-status-bar') + implementation project(':ebarooni-capacitor-calendar') } diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index c6daa303..16d273c1 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -50,5 +50,9 @@ { "pkg": "@capacitor/status-bar", "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin" + }, + { + "pkg": "@ebarooni/capacitor-calendar", + "classpath": "dev.barooni.capacitor.calendar.CapacitorCalendarPlugin" } ] diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 9c006964..77d4bff3 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -40,3 +40,6 @@ project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capa include ':capacitor-status-bar' project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') + +include ':ebarooni-capacitor-calendar' +project(':ebarooni-capacitor-calendar').projectDir = new File('../node_modules/@ebarooni/capacitor-calendar/android') From 0a1a943db8d57b072ba0ee49f03eb947d485a2c6 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 10:20:38 -0500 Subject: [PATCH 2/7] Resolve dev baseUrl to host loopback on Android emulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Android emulator runs in a QEMU VM with its own network stack, so baseUrl 127.0.0.1:8000 in the dev environment resolved to the emulator itself and the schedule could never load. iOS simulator shares the host network namespace so it did not hit this. Detect the platform at module load via Capacitor.getPlatform() and substitute 10.0.2.2 on Android — the emulator's documented alias for the host loopback — so `ionic cap run --configuration=development` reaches the local pycon server on both platforms without any per-run adb reverse. --- src/environments/environment.dev.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/environments/environment.dev.ts b/src/environments/environment.dev.ts index 9e4a8dc8..4486b1a4 100644 --- a/src/environments/environment.dev.ts +++ b/src/environments/environment.dev.ts @@ -1,7 +1,14 @@ +import { Capacitor } from '@capacitor/core'; + +// Android emulator runs in a VM with its own loopback, so 127.0.0.1 points +// at the emulator itself. 10.0.2.2 is the emulator's alias for the host +// loopback. iOS simulator shares the host network stack, so 127.0.0.1 works. +const devHost = Capacitor.getPlatform() === 'android' ? '10.0.2.2' : '127.0.0.1'; + export const environment = { name: 'development', production: false, - baseUrl: 'http://127.0.0.1:8000', + baseUrl: `http://${devHost}:8000`, storageKey: '__pycon_us_mobile_development_2026', timezone: 'America/Los_Angeles', utcOffset: -7, From f22adc761138fbe487bee51ed4e885407545485e Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 10:24:51 -0500 Subject: [PATCH 3/7] Resolve relative speaker photo URLs against baseUrl The backend returns speaker photos as site-relative paths such as /2026/media/speaker_photos/3.webp.256x256_q85.jpg. Rendering those in an resolves them against the document origin, which is fine in production (origin == us.pycon.org == the API host) but breaks under ionic livereload, where the origin is the dev server (e.g. 10.0.2.2:8100) and cannot serve media files. Normalize photo URLs to absolute when processing the schedule so images load regardless of the WebView origin. Absolute http(s) URLs are passed through unchanged, so existing S3-backed headshots still work. --- src/app/providers/conference-data.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/providers/conference-data.ts b/src/app/providers/conference-data.ts index 1e39b823..085aafbe 100644 --- a/src/app/providers/conference-data.ts +++ b/src/app/providers/conference-data.ts @@ -42,6 +42,11 @@ export class ConferenceData { return this.trackIcons[trackName] || 'mic-outline'; } + private resolveSpeakerPhoto(photo?: string | null): string { + if (!photo) return 'assets/img/person-circle-outline.png'; + return /^https?:\/\//.test(photo) ? photo : `${environment.baseUrl}${photo}`; + } + constructor( public http: HttpClient, public user: UserData, @@ -234,7 +239,7 @@ export class ConferenceData { "name": speaker.name, // only display the speaker photo if it's not null in the response. // otherwise, show a default fallback photo - "profilePic": speaker.photo ? speaker.photo : 'assets/img/person-circle-outline.png', + "profilePic": this.resolveSpeakerPhoto(speaker.photo), "about": speaker.bio, }); } @@ -329,7 +334,7 @@ export class ConferenceData { "name": speaker.name, // only display the speaker photo if it's not null in the response. // otherwise, show a default fallback photo - "profilePic": speaker.photo ? speaker.photo : 'assets/img/person-circle-outline.png', + "profilePic": this.resolveSpeakerPhoto(speaker.photo), "about": speaker.bio, }); } From 8fadbcbb73be8a1b08c6341d19e29ebf884121d3 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 10:28:44 -0500 Subject: [PATCH 4/7] Match row height for img-based social icons on Android Ionic's MD mode applies vertical margin only to ion-icon[slot="start"], not to raw . The Bluesky, X, and PyPI rows therefore rendered shorter than the ion-icon rows on Android, producing visible spacing inconsistency in the Social Media list. iOS mode was unaffected because its flex layout centers start-slot content without relying on margin. Add align-self: center and 12px block margins to .social-icon so the img-based items match ion-icon row height on both platforms. --- src/app/pages/social-media/social-media.page.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/pages/social-media/social-media.page.scss b/src/app/pages/social-media/social-media.page.scss index b9dd72c1..62c38272 100644 --- a/src/app/pages/social-media/social-media.page.scss +++ b/src/app/pages/social-media/social-media.page.scss @@ -61,6 +61,11 @@ ion-title { width: 24px; height: 24px; margin-inline-end: 16px; + // Ionic's MD mode only applies top/bottom margins to ion-icon[slot="start"], + // not raw — without this the Bluesky/X/PyPI rows collapse shorter than + // the ion-icon-based rows on Android. + align-self: center; + margin-block: 12px; } .social-icon.bluesky { From 598dc781163b7aa0372cb27dcc2052c0fc53eccf Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 11:51:34 -0500 Subject: [PATCH 5/7] bump vers --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 249b02b7..150fd63b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ android { applicationId "org.pycon.us" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 260000 + versionCode 26007 versionName "26.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { From ea0098c062243ddc5b0cbcef5354e3aad8e8f501 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 11:54:27 -0500 Subject: [PATCH 6/7] Bump CameraX to 1.4.2 for 16 KB page alignment Google Play Console rejects production releases whose native libraries are not aligned for 16 KB memory pages. Verified against the built AAB: the offending library was libimage_processing_util_jni.so, shipped by androidx.camera. @capacitor-mlkit/barcode-scanning pins CameraX to 1.1.0 (2022), whose LOAD segments are 4 KB-aligned (2**12). CameraX 1.4.0+ emits 16 KB-aligned native libs (2**14). The MLKit plugin reads each CameraX artifact version from rootProject.ext, so overriding here propagates without patching node_modules. Post-bump AAB inspection via objdump confirms all three native libraries (libbarhopper_v3, libimage_processing_util_jni, libsurface_util_jni) are 16 KB-aligned. --- android/variables.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/android/variables.gradle b/android/variables.gradle index 72e809dc..81edf872 100644 --- a/android/variables.gradle +++ b/android/variables.gradle @@ -13,4 +13,13 @@ ext { androidxJunitVersion = '1.2.1' androidxEspressoCoreVersion = '3.6.1' cordovaAndroidVersion = '10.1.1' + + // Override @capacitor-mlkit/barcode-scanning's CameraX default (1.1.0). + // Play Console rejects releases whose native libraries are not aligned + // for 16 KB memory pages — libimage_processing_util_jni.so at CameraX + // 1.1.0 is 4 KB-aligned. 1.4.0+ ships 16 KB-aligned libs. + androidxCameraCamera2Version = '1.4.2' + androidxCameraCoreVersion = '1.4.2' + androidxCameraLifecycleVersion = '1.4.2' + androidxCameraViewVersion = '1.4.2' } From 12ca9ba63ceb6e9868229abeea65f65bf762666a Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Fri, 24 Apr 2026 13:35:27 -0500 Subject: [PATCH 7/7] bump vers --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 150fd63b..714ff96e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ android { applicationId "org.pycon.us" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 26007 + versionCode 26009 versionName "26.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions {