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
2 changes: 2 additions & 0 deletions SeforimApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ kotlin {
implementation(libs.hebrew.numerals)
api(project(":jewel"))
implementation(project(":earthwidget"))
implementation(libs.nucleus.system.color)
implementation(libs.nucleus.decorated.window)
implementation(libs.nucleus.graalvm.runtime)
implementation(libs.nucleus.updater.runtime)
implementation(libs.nucleus.energy.manager)
implementation(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
}
Expand Down
26 changes: 26 additions & 0 deletions SeforimApp/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,33 @@
native <methods>;
}

-keep class io.github.kdroidfilter.nucleus.energymanager.** { *; }

# macOS
-keep class io.github.kdroidfilter.nucleus.systemcolor.mac.NativeMacSystemColorBridge {
native <methods>;
static void onAccentColorChanged(float, float, float);
static void onContrastChanged(boolean);
}

# Windows
-keep class io.github.kdroidfilter.nucleus.systemcolor.windows.NativeWindowsSystemColorBridge {
native <methods>;
static void onAccentColorChanged(int, int, int);
static void onHighContrastChanged(boolean);
}

# Linux
-keep class io.github.kdroidfilter.nucleus.systemcolor.linux.NativeLinuxSystemColorBridge {
native <methods>;
static void onAccentColorChanged(float, float, float);
static void onHighContrastChanged(boolean);
}

-keep class io.github.kdroidfilter.nucleus.systemcolor.** { *; }

# --- Sentry crash reporting SDK ---
# Sentry uses reflection for serialization and event processing.
-keep class io.sentry.** { *; }
-dontwarn io.sentry.**

7 changes: 7 additions & 0 deletions SeforimApp/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@
<string name="settings_theme_style_label">סגנון ערכת נושא</string>
<string name="settings_theme_style_classic">קלאסי</string>
<string name="settings_theme_style_islands">איילנדס</string>
<!-- Accent color selector -->
<string name="settings_accent_color_label">צבע מבטא</string>
<string name="accent_color_system">מערכת</string>
<string name="accent_color_default">כחול</string>
<string name="accent_color_teal">טורקיז</string>
<string name="accent_color_green">ירוק</string>
<string name="accent_color_gold">זהב</string>

<!-- Onboarding -->
<string name="onboarding_title_bar">מדריך ההגדרה של זית</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.kdroidfilter.seforimapp.core

import androidx.compose.runtime.Stable
import io.github.kdroidfilter.seforimapp.core.presentation.theme.AccentColor
import io.github.kdroidfilter.seforimapp.core.presentation.theme.IntUiThemes
import io.github.kdroidfilter.seforimapp.core.presentation.theme.ThemeStyle
import io.github.kdroidfilter.seforimapp.core.settings.AppSettings
Expand Down Expand Up @@ -30,6 +31,14 @@ class MainAppState {
AppSettings.setThemeStyle(style)
}

private val _accentColor = MutableStateFlow(AppSettings.getAccentColor())
val accentColor: StateFlow<AccentColor> = _accentColor.asStateFlow()

fun setAccentColor(accent: AccentColor) {
_accentColor.value = accent
AppSettings.setAccentColor(accent)
}

private val _showOnboarding = MutableStateFlow<Boolean?>(null)
val showOnBoarding: StateFlow<Boolean?> = _showOnboarding.asStateFlow()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
@file:OptIn(ExperimentalJewelApi::class)

package io.github.kdroidfilter.seforimapp.core.presentation.components

import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.compose.resources.Font
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
import org.jetbrains.jewel.foundation.code.highlighting.NoOpCodeHighlighter
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.LocalTextStyle
import org.jetbrains.jewel.intui.markdown.standalone.ProvideMarkdownStyling
import org.jetbrains.jewel.intui.markdown.standalone.dark
import org.jetbrains.jewel.intui.markdown.standalone.light
import org.jetbrains.jewel.intui.markdown.standalone.styling.dark
import org.jetbrains.jewel.intui.markdown.standalone.styling.light
import org.jetbrains.jewel.markdown.MarkdownBlock
import org.jetbrains.jewel.markdown.extensions.autolink.AutolinkProcessorExtension
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.jetbrains.jewel.markdown.rendering.InlinesStyling
import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer
import org.jetbrains.jewel.markdown.rendering.MarkdownStyling
import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer
import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding
import org.jetbrains.jewel.ui.typography
import seforimapp.seforimapp.generated.resources.Res
import seforimapp.seforimapp.generated.resources.notoserifhebrew
import java.awt.Desktop.getDesktop
import java.net.URI.create

/**
* Renders a markdown resource file with accent-colored links and the app's Hebrew font.
*
* @param resourcePath path inside composeResources (e.g. "files/ABOUT.md")
* @param modifier modifier for the outer container
* @param includeH3 whether to include custom h3 heading styling (About/Conditions use it, Licence doesn't)
* @param extraItems additional items appended after the markdown blocks (e.g. a checkbox)
*/
@Composable
fun AccentMarkdownView(
resourcePath: String,
modifier: Modifier = Modifier,
includeH3: Boolean = true,
extraItems: (LazyListScope.() -> Unit)? = null,
) {
val isDark = JewelTheme.isDark
val accent = JewelTheme.globalColors.outlines.focused

val textStyle =
LocalTextStyle.current.copy(
fontFamily = FontFamily(Font(resource = Res.font.notoserifhebrew)),
fontSize = 14.sp,
)

val h2TextStyle =
LocalTextStyle.current.copy(
fontFamily = FontFamily(Font(resource = Res.font.notoserifhebrew)),
fontSize = JewelTheme.typography.h2TextStyle.fontSize,
)

val h3TextStyle =
LocalTextStyle.current.copy(
fontFamily = FontFamily(Font(resource = Res.font.notoserifhebrew)),
fontSize = JewelTheme.typography.h3TextStyle.fontSize,
)

val h2Padding = PaddingValues(top = 12.dp, bottom = 8.dp)
val h3Padding = PaddingValues(top = 10.dp, bottom = 6.dp)

val linkStyle = SpanStyle(color = accent)
val linkHoveredStyle = SpanStyle(color = accent, textDecoration = TextDecoration.Underline)

val markdownStyling =
remember(isDark, textStyle, accent, includeH3) {
val inlines =
if (isDark) {
InlinesStyling.dark(textStyle, link = linkStyle, linkHovered = linkHoveredStyle, linkVisited = linkStyle)
} else {
InlinesStyling.light(textStyle, link = linkStyle, linkHovered = linkHoveredStyle, linkVisited = linkStyle)
}

val heading = buildHeading(isDark, textStyle, h2TextStyle, h2Padding, h3TextStyle, h3Padding, includeH3)

if (isDark) {
MarkdownStyling.dark(
baseTextStyle = textStyle,
inlinesStyling = inlines,
blockVerticalSpacing = 8.dp,
heading = heading,
)
} else {
MarkdownStyling.light(
baseTextStyle = textStyle,
inlinesStyling = inlines,
blockVerticalSpacing = 8.dp,
heading = heading,
)
}
}

val processor =
remember {
MarkdownProcessor(listOf(AutolinkProcessorExtension))
}

val blockRenderer =
remember(markdownStyling) {
if (isDark) {
MarkdownBlockRenderer.dark(styling = markdownStyling)
} else {
MarkdownBlockRenderer.light(styling = markdownStyling)
}
}

var blocks by remember { mutableStateOf(emptyList<MarkdownBlock>()) }
LaunchedEffect(resourcePath) {
val bytes = Res.readBytes(resourcePath)
blocks = processor.processMarkdownDocument(bytes.decodeToString())
}

ProvideMarkdownStyling(markdownStyling, blockRenderer, NoOpCodeHighlighter) {
val lazyListState = rememberLazyListState()
VerticallyScrollableContainer(lazyListState as ScrollableState) {
LazyColumn(
modifier = modifier.fillMaxSize(),
state = lazyListState,
contentPadding =
PaddingValues(
start = 8.dp,
top = 8.dp,
end = 8.dp + scrollbarContentSafePadding(),
bottom = 16.dp,
),
verticalArrangement = Arrangement.spacedBy(markdownStyling.blockVerticalSpacing),
) {
items(blocks) { block ->
blockRenderer.RenderBlock(
block = block,
enabled = true,
onUrlClick = { url: String -> getDesktop().browse(create(url)) },
modifier = Modifier.fillMaxWidth(),
)
}
extraItems?.invoke(this)
}
}
}
}

private fun buildHeading(
isDark: Boolean,
textStyle: TextStyle,
h2TextStyle: TextStyle,
h2Padding: PaddingValues,
h3TextStyle: TextStyle,
h3Padding: PaddingValues,
includeH3: Boolean,
): MarkdownStyling.Heading {
val h2 =
if (isDark) {
MarkdownStyling.Heading.H2.dark(baseTextStyle = h2TextStyle, padding = h2Padding)
} else {
MarkdownStyling.Heading.H2.light(baseTextStyle = h2TextStyle, padding = h2Padding)
}

val h3 =
if (includeH3) {
if (isDark) {
MarkdownStyling.Heading.H3.dark(baseTextStyle = h3TextStyle, padding = h3Padding)
} else {
MarkdownStyling.Heading.H3.light(baseTextStyle = h3TextStyle, padding = h3Padding)
}
} else {
null
}

return if (isDark) {
if (h3 != null) {
MarkdownStyling.Heading.dark(baseTextStyle = textStyle, h2 = h2, h3 = h3)
} else {
MarkdownStyling.Heading.dark(baseTextStyle = textStyle, h2 = h2)
}
} else {
if (h3 != null) {
MarkdownStyling.Heading.light(baseTextStyle = textStyle, h2 = h2, h3 = h3)
} else {
MarkdownStyling.Heading.light(baseTextStyle = textStyle, h2 = h2)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.github.kdroidfilter.nucleus.window.DecoratedWindowScope
import io.github.kdroidfilter.nucleus.window.TitleBar
import io.github.kdroidfilter.nucleus.window.macOSLargeCornerRadius
import io.github.kdroidfilter.nucleus.window.newFullscreenControls
import io.github.kdroidfilter.nucleus.window.styling.LocalTitleBarStyle
import io.github.kdroidfilter.platformtools.OperatingSystem
Expand All @@ -22,7 +23,7 @@ import io.github.kdroidfilter.seforimapp.framework.platform.PlatformInfo
@Composable
fun DecoratedWindowScope.MainTitleBar() {
TitleBar(
modifier = Modifier.newFullscreenControls(),
modifier = Modifier.newFullscreenControls().macOSLargeCornerRadius(),
gradientStartColor = if (ThemeUtils.isIslandsStyle()) ThemeUtils.titleBarGradientColor() else Color.Unspecified,
) {
// Window control buttons (close/maximize/minimize) are Compose-based on Linux and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@ fun TelescopeIconButton(
withPadding: Boolean = true,
enabled: Boolean = true,
) {
val accent = JewelTheme.globalColors.outlines.focused
val backgroundColor by animateColorAsState(
targetValue =
when {
!enabled -> Color(0xFF0E639C).copy(alpha = 0.5f)
isSelected -> Color(0xFF0E639C)
!enabled -> accent.copy(alpha = 0.5f)
isSelected -> accent
else -> Color.Transparent
},
animationSpec = tween(200),
Expand Down
Loading
Loading