diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ec6f9038f..c65abfdde 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -28,6 +28,10 @@ kotlin { implementation(projects.core.domain) implementation(projects.core.presentation) + // Coil SVG decoder registered in App() via SingletonImageLoaderFactory + implementation(libs.coil3.compose) + implementation(libs.coil3.svg) + implementation(projects.feature.apps.domain) implementation(projects.feature.apps.data) implementation(projects.feature.apps.presentation) diff --git a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.kt b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.kt index 3091b8508..a0c34969c 100644 --- a/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.kt +++ b/composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.kt @@ -33,6 +33,16 @@ import zed.rainxch.githubstore.app.whatsnew.WhatsNewViewModel @Composable fun App(deepLinkUri: String? = null) { + // Wire Coil's singleton ImageLoader with the SVG decoder so README + // images that point to .svg URLs (shields.io badges, diagrams, + // hero images) render natively instead of failing silently. + coil3.compose.setSingletonImageLoaderFactory { context -> + coil3.ImageLoader + .Builder(context) + .components { add(coil3.svg.SvgDecoder.Factory()) } + .build() + } + val viewModel: MainViewModel = koinViewModel() val state by viewModel.state.collectAsStateWithLifecycle() diff --git a/core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/util/applyThemeAwareImages.kt b/core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/util/applyThemeAwareImages.kt new file mode 100644 index 000000000..9105f380c --- /dev/null +++ b/core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/util/applyThemeAwareImages.kt @@ -0,0 +1,50 @@ +package zed.rainxch.core.domain.util + +fun applyThemeAwareImages( + content: String, + isDark: Boolean, +): String { + if (content.isEmpty()) return content + var processed = content + + // Strip markdown-image entries whose URL has `#gh-dark-mode-only` + // or `#gh-light-mode-only` and we're on the OTHER theme. GitHub's + // own renderer hides these on the mismatched theme; emulating that + // keeps READMEs from showing both light + dark variants stacked. + processed = processed.replace( + Regex("""!\[([^\]]*)\]\(([^)]*?)#gh-(dark|light)-mode-only([^)]*)\)"""), + ) { match -> + val mode = match.groupValues[3] + if ((isDark && mode == "light") || (!isDark && mode == "dark")) { + "" // drop entirely; alt text would just be noise here + } else { + // Strip the fragment so the URL is clean for Coil's cache key. + val alt = match.groupValues[1] + val urlBase = match.groupValues[2] + val trailing = match.groupValues[4] + "![$alt]($urlBase$trailing)" + } + } + + // Same for raw HTML tags. + processed = processed.replace( + Regex( + """]*?)src\s*=\s*(["'])([^"']*?)#gh-(dark|light)-mode-only([^"']*?)\2([^>]*?)>""", + RegexOption.IGNORE_CASE, + ), + ) { match -> + val mode = match.groupValues[4] + if ((isDark && mode == "light") || (!isDark && mode == "dark")) { + "" + } else { + val pre = match.groupValues[1] + val quote = match.groupValues[2] + val urlBase = match.groupValues[3] + val trailing = match.groupValues[5] + val post = match.groupValues[6] + "" + } + } + + return processed +} diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/18.json index 11f2dc565..056d701f3 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/18.json @@ -31,7 +31,8 @@ "Manually linking apps — sorted by installer source (F-Droid / Obtainium first, Play Store and system updates last) with a chip showing each app's source.", "Manual link now suggests matching GitHub repos automatically — pick an app, get ranked candidates, tap to link. Manual URL entry still available.", "GitHub-style alert callouts in README and release notes — Note, Tip, Important, Warning, Caution now render as tinted cards with icons instead of literal text.", - "Emoji shortcodes in README and release notes — :rocket: now renders as 🚀, :tada: as 🎉, and ~250 others. Common dev/status icons covered out of the box." + "Emoji shortcodes in README and release notes — :rocket: now renders as 🚀, :tada: as 🎉, and ~250 others. Common dev/status icons covered out of the box.", + "SVG images in README and release notes now render natively (diagrams, hero images). Theme-only images respect light/dark — no more both-variants-stacked. Browser-like User-Agent fixes hotlink-blocked badges from common CDNs." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json index 20184799b..a0c7e70a3 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json @@ -31,7 +31,8 @@ "عند الربط اليدوي للتطبيقات — يتم الفرز حسب مصدر التثبيت (F-Droid / Obtainium أولاً، Play Store وتحديثات النظام أخيراً) مع شارة تُظهر مصدر كل تطبيق.", "الربط اليدوي يقترح الآن مستودعات GitHub المطابقة تلقائياً — اختر تطبيقاً، احصل على مرشحين مرتبين، انقر للربط. الإدخال اليدوي للعنوان لا يزال متاحاً.", "تنبيهات GitHub في README وملاحظات الإصدار — Note وTip وImportant وWarning وCaution تظهر الآن كبطاقات ملونة مع أيقونات بدلاً من نص حرفي.", - "اختصارات الإيموجي في README وملاحظات الإصدار — :rocket: تظهر الآن كـ 🚀، :tada: كـ 🎉، و~250 اختصاراً آخر." + "اختصارات الإيموجي في README وملاحظات الإصدار — :rocket: تظهر الآن كـ 🚀، :tada: كـ 🎉، و~250 اختصاراً آخر.", + "صور SVG في README وملاحظات الإصدار تظهر الآن أصلياً (مخططات، صور رئيسية). الصور الخاصة بسمة معينة تحترم الفاتح/الداكن. User-Agent يشبه المتصفح يصلح شارات CDN المحجوبة." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json index b4c301faa..8e928289f 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json @@ -31,7 +31,8 @@ "ম্যানুয়াল লিঙ্কে — ইনস্টলার উৎস অনুযায়ী সাজানো (F-Droid / Obtainium প্রথমে, Play Store ও সিস্টেম আপডেট শেষে), প্রতিটি অ্যাপের উৎস দেখাতে চিপ যুক্ত।", "ম্যানুয়াল লিঙ্ক এখন স্বয়ংক্রিয়ভাবে মিলে যাওয়া GitHub রিপো সাজেস্ট করে — অ্যাপ বেছে নিন, র‍্যাংকড প্রার্থী পান, লিঙ্ক করতে ট্যাপ করুন। ম্যানুয়াল URL এন্ট্রি এখনও উপলব্ধ।", "README ও রিলিজ নোটে GitHub-স্টাইল অ্যালার্ট কলআউট — Note, Tip, Important, Warning, Caution এখন আইকনসহ রঙিন কার্ড হিসেবে দেখায়, আগে শুধু টেক্সট ছিল।", - "README ও রিলিজ নোটে ইমোজি শর্টকোড — :rocket: এখন 🚀, :tada: এখন 🎉, এবং আরও ~250টি।" + "README ও রিলিজ নোটে ইমোজি শর্টকোড — :rocket: এখন 🚀, :tada: এখন 🎉, এবং আরও ~250টি।", + "README ও রিলিজ নোটে SVG ছবি এখন নেটিভভাবে রেন্ডার হয় (ডায়াগ্রাম, হিরো ইমেজ)। থিম-নির্দিষ্ট ছবি লাইট/ডার্ক মেনে চলে। ব্রাউজারের মতো User-Agent CDN-ব্লকড ব্যাজ ঠিক করে।" ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json index 22ddbec88..fae413eab 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json @@ -31,7 +31,8 @@ "Al vincular apps manualmente — orden por origen del instalador (F-Droid / Obtainium primero, Play Store y actualizaciones del sistema al final) con un chip que muestra el origen de cada app.", "El enlace manual ahora sugiere repos de GitHub coincidentes automáticamente — elige una app, obtén candidatos clasificados, toca para vincular. Sigue disponible la entrada manual de URL.", "Llamadas de alerta estilo GitHub en README y notas de versión — Note, Tip, Important, Warning, Caution se muestran como tarjetas tintadas con iconos en vez de texto literal.", - "Atajos de emoji en README y notas de versión — :rocket: ahora se ve como 🚀, :tada: como 🎉, y ~250 más." + "Atajos de emoji en README y notas de versión — :rocket: ahora se ve como 🚀, :tada: como 🎉, y ~250 más.", + "Imágenes SVG en README y notas de versión ahora se renderizan nativamente (diagramas, hero). Imágenes de tema único respetan claro/oscuro. User-Agent de navegador desbloquea badges de CDN." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json index df9f5575b..b263d5b51 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json @@ -31,7 +31,8 @@ "Lors du lien manuel — tri par source d'installation (F-Droid / Obtainium d'abord, Play Store et mises à jour système en dernier) avec une puce indiquant la source de chaque app.", "Le lien manuel suggère désormais automatiquement les dépôts GitHub correspondants — choisis une app, obtiens des candidats classés, touche pour lier. Saisie d'URL manuelle toujours disponible.", "Encadrés d'alerte style GitHub dans README et notes de version — Note, Tip, Important, Warning, Caution s'affichent en cartes teintées avec icônes plutôt qu'en texte brut.", - "Raccourcis emoji dans README et notes de version — :rocket: s'affiche en 🚀, :tada: en 🎉, et ~250 autres." + "Raccourcis emoji dans README et notes de version — :rocket: s'affiche en 🚀, :tada: en 🎉, et ~250 autres.", + "Les images SVG dans README et notes de version s'affichent maintenant nativement (diagrammes, héros). Les images mono-thème respectent clair/sombre. User-Agent navigateur débloque les badges CDN." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json index c1025a48b..f5e0fd1b2 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json @@ -31,7 +31,8 @@ "मैन्युअल लिंक में — इंस्टॉलर स्रोत के अनुसार क्रम (F-Droid / Obtainium पहले, Play Store और सिस्टम अपडेट अंत में), हर ऐप के स्रोत को दिखाने वाली चिप के साथ।", "मैन्युअल लिंक अब अपने आप मेल खाते GitHub रिपो सुझाता है — कोई ऐप चुनें, रैंक किए हुए उम्मीदवार पाएं, लिंक करने के लिए टैप करें। मैन्युअल URL एंट्री अब भी उपलब्ध है।", "README और रिलीज़ नोट्स में GitHub-शैली अलर्ट कॉलआउट — Note, Tip, Important, Warning, Caution अब आइकन के साथ रंगीन कार्ड के रूप में दिखेंगे, पहले सिर्फ टेक्स्ट थे।", - "README और रिलीज़ नोट्स में इमोजी शॉर्टकोड — :rocket: अब 🚀 दिखेगा, :tada: 🎉, और ~250 अन्य।" + "README और रिलीज़ नोट्स में इमोजी शॉर्टकोड — :rocket: अब 🚀 दिखेगा, :tada: 🎉, और ~250 अन्य।", + "README और रिलीज़ नोट्स में SVG इमेज अब नेटिव रूप से रेंडर होती हैं (डायग्राम, हीरो)। थीम-विशिष्ट इमेज लाइट/डार्क का सम्मान करती हैं। ब्राउज़र-समान User-Agent CDN-ब्लॉक्ड बैज ठीक करता है।" ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json index b5d35cfe4..e1d80ab03 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json @@ -31,7 +31,8 @@ "Collegamento manuale di app — ordinate per origine di installazione (F-Droid / Obtainium per primi, Play Store e aggiornamenti di sistema per ultimi) con un chip che mostra l'origine di ogni app.", "Il collegamento manuale ora suggerisce automaticamente repo GitHub corrispondenti — scegli un'app, ottieni candidati classificati, tocca per collegare. L'inserimento manuale dell'URL resta disponibile.", "Avvisi stile GitHub in README e note di rilascio — Note, Tip, Important, Warning, Caution ora si vedono come card colorate con icone invece di testo letterale.", - "Codici emoji in README e note di rilascio — :rocket: ora appare come 🚀, :tada: come 🎉, e ~250 altri." + "Codici emoji in README e note di rilascio — :rocket: ora appare come 🚀, :tada: come 🎉, e ~250 altri.", + "Le immagini SVG in README e note di rilascio ora si renderizzano nativamente (diagrammi, hero). Le immagini per tema rispettano chiaro/scuro. User-Agent da browser sblocca i badge CDN." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json index 48d5a43f1..b65b68391 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json @@ -31,7 +31,8 @@ "手動リンク — インストーラ別に並び替え(F-Droid / Obtainium が先、Play ストアやシステム更新は最後)し、各アプリの入手元をチップで表示。", "手動リンクが一致する GitHub リポを自動提案 — アプリを選ぶとランク付き候補が出る。タップでリンク。手動 URL 入力も引き続き利用可能。", "README やリリースノートで GitHub スタイルのアラートが表示されるように — Note、Tip、Important、Warning、Caution がアイコン付きの色付きカードでレンダリングされます。", - "README とリリースノートで絵文字ショートコードに対応 — :rocket: は 🚀、:tada: は 🎉、合計約 250 種類。" + "README とリリースノートで絵文字ショートコードに対応 — :rocket: は 🚀、:tada: は 🎉、合計約 250 種類。", + "README とリリースノートの SVG 画像をネイティブに描画(図、ヒーロー画像)。テーマ専用画像はライト/ダークを尊重。ブラウザ風 User-Agent で CDN ホットリンクブロックを回避。" ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json index a15b93755..0c22c4a42 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json @@ -31,7 +31,8 @@ "수동 연결 — 설치 출처별 정렬(F-Droid / Obtainium 먼저, Play 스토어와 시스템 업데이트는 마지막)에 각 앱의 출처를 보여주는 칩 표시.", "수동 연결이 일치하는 GitHub 리포를 자동으로 제안합니다 — 앱 선택 후 순위 후보를 받고 탭하여 연결. 수동 URL 입력도 그대로 사용 가능.", "README와 릴리스 노트에서 GitHub 스타일 알림 카드 — Note, Tip, Important, Warning, Caution이 아이콘과 함께 색상 카드로 표시됩니다.", - "README와 릴리스 노트에서 이모지 단축코드 지원 — :rocket: 은 🚀, :tada: 는 🎉, 총 약 250개." + "README와 릴리스 노트에서 이모지 단축코드 지원 — :rocket: 은 🚀, :tada: 는 🎉, 총 약 250개.", + "README와 릴리스 노트의 SVG 이미지가 네이티브로 렌더링됩니다 (다이어그램, 히어로). 테마 전용 이미지는 라이트/다크를 따릅니다. 브라우저 같은 User-Agent로 CDN 핫링크 차단 우회." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json index 47d59719d..11913ca1e 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json @@ -31,7 +31,8 @@ "Ręczne powiązywanie aplikacji — sortowane wg źródła instalatora (F-Droid / Obtainium na początku, Play Store i aktualizacje systemu na końcu) z chipem pokazującym źródło każdej aplikacji.", "Ręczne powiązanie automatycznie proponuje pasujące repozytoria GitHub — wybierz aplikację, otrzymaj uszeregowanych kandydatów, dotknij, aby powiązać. Ręczne wpisanie URL nadal dostępne.", "Wyróżnienia w stylu GitHub w README i notatkach wydania — Note, Tip, Important, Warning, Caution renderują się teraz jako kolorowe karty z ikonami zamiast surowego tekstu.", - "Skróty emoji w README i notatkach wydania — :rocket: pokazuje się jako 🚀, :tada: jako 🎉, łącznie ~250 skrótów." + "Skróty emoji w README i notatkach wydania — :rocket: pokazuje się jako 🚀, :tada: jako 🎉, łącznie ~250 skrótów.", + "Obrazy SVG w README i notatkach wydania renderują się teraz natywnie (diagramy, hero). Obrazy tylko-jasny/tylko-ciemny respektują motyw. Przeglądarkowy User-Agent odblokuje badge'e CDN." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json index 4427f2833..252646edb 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json @@ -31,7 +31,8 @@ "Ручная привязка приложений — сортировка по источнику установки (F-Droid / Obtainium первыми, Play Маркет и системные обновления — последними), с шильдиком источника для каждого приложения.", "Ручная привязка теперь автоматически предлагает подходящие GitHub-репозитории — выберите приложение, получите ранжированных кандидатов, нажмите для привязки. Ручной ввод URL по-прежнему доступен.", "Выделения в стиле GitHub в README и заметках о выпуске — Note, Tip, Important, Warning, Caution теперь отображаются цветными карточками с иконками вместо обычного текста.", - "Короткие коды эмодзи в README и заметках о выпуске — :rocket: теперь 🚀, :tada: — 🎉, всего около 250 кодов." + "Короткие коды эмодзи в README и заметках о выпуске — :rocket: теперь 🚀, :tada: — 🎉, всего около 250 кодов.", + "SVG-изображения в README и заметках о выпуске рендерятся нативно (диаграммы, hero). Тематические изображения учитывают светлую/тёмную тему. Браузерный User-Agent обходит блокировку хотлинков CDN." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json index 35900c2be..84a8d89bb 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json @@ -31,7 +31,8 @@ "Uygulamayı manuel bağlarken — yükleyici kaynağına göre sıralı (önce F-Droid / Obtainium, en sonda Play Store ve sistem güncellemeleri); her uygulamanın kaynağını gösteren çip eklendi.", "Manuel bağlama artık eşleşen GitHub depolarını otomatik öneriyor — uygulamayı seçin, sıralı adaylar alın, bağlamak için dokunun. URL'yi manuel girme seçeneği de duruyor.", "README ve sürüm notlarında GitHub tarzı uyarı kutuları — Note, Tip, Important, Warning, Caution artık düz metin yerine ikonlu renkli kartlar olarak görünüyor.", - "README ve sürüm notlarında emoji kısayolları — :rocket: artık 🚀 olarak, :tada: 🎉 olarak ve ~250 başka kısayol." + "README ve sürüm notlarında emoji kısayolları — :rocket: artık 🚀 olarak, :tada: 🎉 olarak ve ~250 başka kısayol.", + "README ve sürüm notlarındaki SVG resimleri artık doğrudan render ediliyor (diyagram, hero). Tema özel resimler açık/koyu temaya saygı duyuyor. Tarayıcı benzeri User-Agent CDN hotlink engelini aşıyor." ] } ] diff --git a/core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json b/core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json index 5d52f23e9..c7914091a 100644 --- a/core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json +++ b/core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json @@ -31,7 +31,8 @@ "手动链接应用时 — 按安装来源排序(F-Droid / Obtainium 在前,Play 商店与系统更新在后),并通过标签显示每个应用的来源。", "手动链接现可自动推荐匹配的 GitHub 仓库 — 选择应用,获得排序后的候选项,点击即可链接。仍支持手动输入 URL。", "README 与发布说明中的 GitHub 风格警示框 — Note、Tip、Important、Warning、Caution 现在以带图标的彩色卡片呈现,而不是字面文本。", - "README 与发布说明中的 emoji 简写 — :rocket: 现在显示为 🚀,:tada: 为 🎉,共约 250 种。" + "README 与发布说明中的 emoji 简写 — :rocket: 现在显示为 🚀,:tada: 为 🎉,共约 250 种。", + "README 与发布说明中的 SVG 图片现可原生渲染(示意图、主图)。主题专属图片遵循浅色/深色。浏览器风 User-Agent 绕过 CDN 防盗链。" ] } ] diff --git a/feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt b/feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt index fc11eed7b..23c5720e0 100644 --- a/feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt +++ b/feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt @@ -42,7 +42,11 @@ fun preprocessMarkdown( (lower.contains("/badge") && isSvgUrl(lower)) } - fun shouldSkipImage(url: String): Boolean = isSvgUrl(url) || isBadgeUrl(url) + // SVGs are no longer skipped wholesale — Coil's SVG decoder now + // handles them (registered in the App composable). Only known badge + // / shields services stay skipped because they're noise even when + // rendered correctly (status badges = clutter on small screens). + fun shouldSkipImage(url: String): Boolean = isBadgeUrl(url) fun resolveUrl(path: String): String { val trimmed = path.trim() diff --git a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt index 47b4374fe..507af9c3d 100644 --- a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt +++ b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt @@ -101,12 +101,17 @@ fun LazyListScope.about( } item { - val displayContent = + val raw = if (translationState.isShowingTranslation && translationState.translatedText != null) { translationState.translatedText } else { readmeMarkdown } + val isDark = androidx.compose.foundation.isSystemInDarkTheme() + val displayContent = + androidx.compose.runtime.remember(raw, isDark) { + zed.rainxch.core.domain.util.applyThemeAwareImages(raw, isDark) + } AnimatedContent( targetState = displayContent, diff --git a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt index bee576527..8b84b33fa 100644 --- a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt +++ b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt @@ -142,12 +142,17 @@ private fun ExpandableMarkdownContent( isExpanded: Boolean, onToggleExpanded: () -> Unit, ) { - val displayContent = + val raw = if (translationState.isShowingTranslation && translationState.translatedText != null) { translationState.translatedText } else { release.description ?: stringResource(Res.string.no_release_notes) } + val isDark = androidx.compose.foundation.isSystemInDarkTheme() + val displayContent = + remember(raw, isDark) { + zed.rainxch.core.domain.util.applyThemeAwareImages(raw, isDark) + } val density = LocalDensity.current val colors = rememberMarkdownColors() diff --git a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/utils/MarkdownImageTransformer.kt b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/utils/MarkdownImageTransformer.kt index 8dc2b42c1..2c8594d62 100644 --- a/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/utils/MarkdownImageTransformer.kt +++ b/feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/utils/MarkdownImageTransformer.kt @@ -6,16 +6,31 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale +import coil3.compose.LocalPlatformContext import coil3.compose.rememberAsyncImagePainter +import coil3.network.NetworkHeaders +import coil3.network.httpHeaders +import coil3.request.ImageRequest import com.mikepenz.markdown.model.ImageData import com.mikepenz.markdown.model.ImageTransformer object MarkdownImageTransformer : ImageTransformer { + private const val BROWSER_UA = + "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/126.0.0.0 Mobile Safari/537.36 GitHubStore/1.8" + + private val networkHeaders = + NetworkHeaders.Builder() + .add("User-Agent", BROWSER_UA) + .add( + "Accept", + "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + ) + .build() + @Composable override fun transform(link: String): ImageData? { - if (link.isBlank()) { - return null - } + if (link.isBlank()) return null val normalizedLink = if (link.contains("github.com") && link.contains("/blob/")) { @@ -26,13 +41,6 @@ object MarkdownImageTransformer : ImageTransformer { link } - if (normalizedLink.endsWith(".svg", ignoreCase = true) || - normalizedLink.contains(".svg?", ignoreCase = true) || - normalizedLink.contains(".svg#", ignoreCase = true) - ) { - return null - } - if (!normalizedLink.startsWith("http://") && !normalizedLink.startsWith("https://") && !normalizedLink.startsWith("data:") @@ -40,10 +48,14 @@ object MarkdownImageTransformer : ImageTransformer { return null } - val painter = - rememberAsyncImagePainter( - model = normalizedLink, - ) + val context = LocalPlatformContext.current + val request = + ImageRequest.Builder(context) + .data(normalizedLink) + .httpHeaders(networkHeaders) + .build() + + val painter = rememberAsyncImagePainter(model = request) return ImageData( painter = painter, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ae87ff7c..107e2bc5d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ moko = "0.20.1" buildkonfig = "0.17.1" markdownRenderer = "0.39.2" landscapist = "2.9.5" +coil3 = "3.3.0" shizuku = "13.1.5" dhizuku = "2.5.4" hidden-api = "4.4.0" @@ -110,6 +111,9 @@ sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sql # Image loading landscapist-image = { module = "com.github.skydoves:landscapist-coil3", version.ref = "landscapist" } landscapist-core = { module = "com.github.skydoves:landscapist-core", version.ref = "landscapist" } +coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" } +coil3-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil3" } +coil3-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3" } #Permission handling moko-permissions = { module = "dev.icerock.moko:permissions", version.ref = "moko" } @@ -211,5 +215,7 @@ ktor-common = [ landscapist = [ "landscapist-core", - "landscapist-image" + "landscapist-image", + "coil3-compose", + "coil3-svg", ] \ No newline at end of file