diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceLayoutScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceLayoutScreen.kt
index 506c4c85cf..a6ae046884 100644
--- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceLayoutScreen.kt
+++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceLayoutScreen.kt
@@ -257,6 +257,29 @@ internal fun AppearanceLayoutScreen(
)
},
)
+ SegmentedListItem(
+ onClick = {
+ state.update(
+ AppearanceKeys.PostActionFixedWidth,
+ !timelineAppearance.postActionFixedWidth,
+ )
+ },
+ shapes = ListItemDefaults.item(),
+ content = {
+ Text(text = stringResource(id = R.string.settings_post_action_fixed_width))
+ },
+ supportingContent = {
+ Text(text = stringResource(id = R.string.settings_post_action_fixed_width_description))
+ },
+ trailingContent = {
+ Switch(
+ checked = timelineAppearance.postActionFixedWidth,
+ onCheckedChange = {
+ state.update(AppearanceKeys.PostActionFixedWidth, it)
+ },
+ )
+ },
+ )
SegmentedListItem(
onClick = toPostActionLayout,
shapes = ListItemDefaults.last(),
diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceMediaScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceMediaScreen.kt
index ed36594395..43c1444535 100644
--- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceMediaScreen.kt
+++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceMediaScreen.kt
@@ -178,6 +178,31 @@ internal fun AppearanceMediaScreen(onBack: () -> Unit) {
},
)
}
+ AnimatedVisibility(timelineAppearance.showMedia) {
+ SegmentedListItem(
+ onClick = {
+ state.update(
+ AppearanceKeys.LimitMediaGridToNine,
+ !timelineAppearance.limitMediaGridToNine,
+ )
+ },
+ shapes = ListItemDefaults.item(),
+ content = {
+ Text(text = stringResource(id = R.string.settings_appearance_limit_media_grid_to_nine))
+ },
+ supportingContent = {
+ Text(text = stringResource(id = R.string.settings_appearance_limit_media_grid_to_nine_description))
+ },
+ trailingContent = {
+ Switch(
+ checked = timelineAppearance.limitMediaGridToNine,
+ onCheckedChange = {
+ state.update(AppearanceKeys.LimitMediaGridToNine, it)
+ },
+ )
+ },
+ )
+ }
AnimatedVisibility(timelineAppearance.showMedia) {
SegmentedListItem(
onClick = {
diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/TimelinePresentationEditor.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/TimelinePresentationEditor.kt
index fffdf22164..81896c95fa 100644
--- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/TimelinePresentationEditor.kt
+++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/TimelinePresentationEditor.kt
@@ -88,6 +88,7 @@ internal fun TimelinePresentationEditor(
appearancePatch.contains(AppearanceKeys.TimelineDisplayMode) ||
appearancePatch.contains(AppearanceKeys.FullWidthPost) ||
appearancePatch.contains(AppearanceKeys.PostActionStyle) ||
+ appearancePatch.contains(AppearanceKeys.PostActionFixedWidth) ||
appearancePatch.contains(AppearanceKeys.ShowNumbers)
val displayOverridesEnabled =
appearancePatch.contains(AppearanceKeys.AbsoluteTimestamp) ||
@@ -99,6 +100,7 @@ internal fun TimelinePresentationEditor(
appearancePatch.contains(AppearanceKeys.ShowSensitiveContent) ||
appearancePatch.contains(AppearanceKeys.ExpandContentWarning) ||
appearancePatch.contains(AppearanceKeys.ExpandMediaSize) ||
+ appearancePatch.contains(AppearanceKeys.LimitMediaGridToNine) ||
appearancePatch.contains(AppearanceKeys.VideoAutoplay)
val themeOverridesEnabled = appearancePatch.contains(AppearanceKeys.AvatarShape)
var showFilterDialog by remember { mutableStateOf(false) }
@@ -217,11 +219,16 @@ internal fun TimelinePresentationEditor(
AppearanceKeys.PostActionStyle,
timelineAppearance.postActionStyle,
).set(AppearanceKeys.ShowNumbers, timelineAppearance.showNumbers)
+ .set(
+ AppearanceKeys.PostActionFixedWidth,
+ timelineAppearance.postActionFixedWidth,
+ )
} else {
appearancePatch.clearAll(
AppearanceKeys.TimelineDisplayMode,
AppearanceKeys.FullWidthPost,
AppearanceKeys.PostActionStyle,
+ AppearanceKeys.PostActionFixedWidth,
AppearanceKeys.ShowNumbers,
)
},
@@ -306,6 +313,20 @@ internal fun TimelinePresentationEditor(
),
)
},
+ shapes = ListItemDefaults.item(),
+ )
+ SwitchSettingsItem(
+ title = stringResource(id = R.string.settings_post_action_fixed_width),
+ description = stringResource(id = R.string.settings_post_action_fixed_width_description),
+ checked = timelineAppearance.postActionFixedWidth,
+ onCheckedChange = {
+ onAppearancePatchChange(
+ appearancePatch.set(
+ AppearanceKeys.PostActionFixedWidth,
+ it,
+ ),
+ )
+ },
shapes = ListItemDefaults.last(),
)
}
@@ -421,6 +442,9 @@ internal fun TimelinePresentationEditor(
).set(
AppearanceKeys.ExpandMediaSize,
timelineAppearance.expandMediaSize,
+ ).set(
+ AppearanceKeys.LimitMediaGridToNine,
+ timelineAppearance.limitMediaGridToNine,
).set(AppearanceKeys.VideoAutoplay, timelineAppearance.videoAutoplay)
} else {
appearancePatch.clearAll(
@@ -428,6 +452,7 @@ internal fun TimelinePresentationEditor(
AppearanceKeys.ShowSensitiveContent,
AppearanceKeys.ExpandContentWarning,
AppearanceKeys.ExpandMediaSize,
+ AppearanceKeys.LimitMediaGridToNine,
AppearanceKeys.VideoAutoplay,
)
},
@@ -480,6 +505,20 @@ internal fun TimelinePresentationEditor(
},
shapes = ListItemDefaults.item(),
)
+ SwitchSettingsItem(
+ title = stringResource(id = R.string.settings_appearance_limit_media_grid_to_nine),
+ description = stringResource(id = R.string.settings_appearance_limit_media_grid_to_nine_description),
+ checked = timelineAppearance.limitMediaGridToNine,
+ onCheckedChange = {
+ onAppearancePatchChange(
+ appearancePatch.set(
+ AppearanceKeys.LimitMediaGridToNine,
+ it,
+ ),
+ )
+ },
+ shapes = ListItemDefaults.item(),
+ )
SwitchSettingsItem(
title = stringResource(id = R.string.settings_appearance_expand_media),
description = stringResource(id = R.string.settings_appearance_expand_media_description),
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 501cfb1fae..089a4ab81f 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -259,6 +259,8 @@
动态操作
自定义操作
选择哪些操作显示在按钮行、更多菜单或保持隐藏
+ 按钮固定宽度
+ 让带数字的操作按钮保持统一宽度
按钮行
更多菜单
隐藏
@@ -293,6 +295,8 @@
总是显示内容警告隐藏的帖子内容
保留媒体比例
在时间轴中以原始宽高比显示媒体
+ 媒体网格最多显示 9 项
+ 每条时间轴动态最多显示 9 项媒体
视频自动播放
自动播放动态中的视频
仅限 Wi-Fi
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 665743a1bd..6223451a9b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -304,6 +304,8 @@
Post actions
Customize actions
Choose which actions appear in the row, More menu, or stay hidden
+ Fixed button width
+ Keep action buttons with counts at a consistent width
Button row
More menu
Hidden
@@ -334,6 +336,8 @@
Always reveal post content hidden by content warnings
Preserve media aspect ratio
Show media at its original aspect ratio in timelines
+ Limit media grids to 9 items
+ Show at most 9 media items in each timeline post
Info
Comments
Recommend
diff --git a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AgentChatView.swift b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AgentChatView.swift
index 9244de7c45..32a7029209 100644
--- a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AgentChatView.swift
+++ b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AgentChatView.swift
@@ -560,12 +560,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: true,
expandMediaSize: false,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: .never,
showLinkPreview: false,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: .hidden,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AppearanceSettingsSections.swift b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AppearanceSettingsSections.swift
index abf8efe7e7..a5a238b460 100644
--- a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AppearanceSettingsSections.swift
+++ b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/AppearanceSettingsSections.swift
@@ -136,6 +136,14 @@ public struct AppearanceLayoutSettingsSection: View
Text("appearance_show_numbers", bundle: FlareAppleUILocalization.bundle)
Text("appearance_show_numbers_description", bundle: FlareAppleUILocalization.bundle)
}
+ Toggle(isOn: Binding(get: {
+ appearance.postActionFixedWidth
+ }, set: { newValue in
+ presenter.state.updatePostActionFixedWidth(value: newValue)
+ })) {
+ Text("post_action_fixed_width", bundle: FlareAppleUILocalization.bundle)
+ Text("post_action_fixed_width_description", bundle: FlareAppleUILocalization.bundle)
+ }
}
postActionLayoutLink()
}
@@ -384,6 +392,14 @@ public struct AppearanceMediaSettingsSection: View {
Text("appearance_expand_media_size", bundle: FlareAppleUILocalization.bundle)
Text("appearance_expand_media_size_description", bundle: FlareAppleUILocalization.bundle)
}
+ Toggle(isOn: Binding(get: {
+ appearance.limitMediaGridToNine
+ }, set: { newValue in
+ presenter.state.updateLimitMediaGridToNine(value: newValue)
+ })) {
+ Text("appearance_limit_media_grid_to_nine", bundle: FlareAppleUILocalization.bundle)
+ Text("appearance_limit_media_grid_to_nine_description", bundle: FlareAppleUILocalization.bundle)
+ }
Toggle(isOn: Binding(get: {
appearance.showSensitiveContent
}, set: { newValue in
diff --git a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/PostActionLayoutSupport.swift b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/PostActionLayoutSupport.swift
index fe04653508..f324eda27e 100644
--- a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/PostActionLayoutSupport.swift
+++ b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/PostActionLayoutSupport.swift
@@ -195,12 +195,14 @@ public extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: expandContentWarning,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: videoAutoplay,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: config,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusActionView.swift b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusActionView.swift
index 81c2d852c6..f485364529 100644
--- a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusActionView.swift
+++ b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusActionView.swift
@@ -19,6 +19,7 @@ public struct StatusActionsView: View {
@Environment(\.timelineAppearance.postActionStyle) private var postActionStyle
@Environment(\.timelineAppearance.showNumbers) private var showNumbers
@Environment(\.timelineAppearance.postActionLayout) private var postActionLayout
+ @Environment(\.timelineAppearance.postActionFixedWidth) private var postActionFixedWidth
@Environment(\.openURL) private var openURL
#if os(macOS)
@ScaledMetric(relativeTo: .callout) private var fontSize = 16
@@ -51,7 +52,7 @@ public struct StatusActionsView: View {
)
}
} else {
- HStack {
+ HStack(spacing: postActionStyle == .stretch ? nil : 4) {
ForEach(0.. some View {
+ if isEnabled {
+ ZStack(alignment: .leading) {
+ StatusActionFixedWidthPlaceholder(icon: icon, fontSize: fontSize)
+ .statusActionContentPadding(isExpanded: true, fontSize: fontSize)
+ .hidden()
+ self
+ }
+ } else {
+ self
+ }
+ }
+
@ViewBuilder
func macOSStatusActionHoverStyle(isEnabled: Bool) -> some View {
#if os(macOS)
@@ -414,6 +450,20 @@ public struct StatusActionIcon: View {
}
}
+private struct StatusActionFixedWidthPlaceholder: View {
+ let icon: UiIcon?
+ let fontSize: CGFloat
+
+ var body: some View {
+ Label {
+ Text("")
+ .frame(minWidth: fontSize * 2.5)
+ } icon: {
+ StatusActionIcon(icon: icon)
+ }
+ }
+}
+
private func castActionMenus(_ value: Any) -> [ActionMenu] {
if let actions = value as? [ActionMenu] {
return actions
@@ -857,12 +907,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: true,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: .never,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusMediaView.swift b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusMediaView.swift
index 151fe6bb67..06476a24bc 100644
--- a/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusMediaView.swift
+++ b/appleApp/Shared/FlareAppleCore/Sources/FlareAppleUI/TimelineItem/StatusMediaView.swift
@@ -12,6 +12,7 @@ struct StatusMediaView: View {
let onMediaClicked: (any UiMedia, Int) -> Void
let cornerRadius: CGFloat
@Environment(\.timelineAppearance.expandMediaSize) private var expandMediaSize
+ @Environment(\.timelineAppearance.limitMediaGridToNine) private var limitMediaGridToNine
@Environment(\.timelineMediaActionHandler) private var timelineMediaActionHandler
@State private var isBlur: Bool
// @State private var selectedIndex: Int? = nil
@@ -32,7 +33,7 @@ struct StatusMediaView: View {
}
var body: some View {
- let visibleData = Array(data.prefix(statusMediaMaxVisibleMediaCount))
+ let visibleData = limitMediaGridToNine ? Array(data.prefix(statusMediaMaxVisibleMediaCount)) : data
let overflowCount = data.count - visibleData.count
AdaptiveGrid(
singleFollowsImageAspect: expandMediaSize,
diff --git a/appleApp/Shared/FlareAppleResource/Resources/Localizable.xcstrings b/appleApp/Shared/FlareAppleResource/Resources/Localizable.xcstrings
index d3dfb23438..a97f5ccbf9 100644
--- a/appleApp/Shared/FlareAppleResource/Resources/Localizable.xcstrings
+++ b/appleApp/Shared/FlareAppleResource/Resources/Localizable.xcstrings
@@ -14178,6 +14178,50 @@
}
}
},
+ "appearance_limit_media_grid_to_nine" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Limit media grids to 9 items"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "媒体网格最多显示 9 项"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "媒體網格最多顯示 9 項"
+ }
+ }
+ }
+ },
+ "appearance_limit_media_grid_to_nine_description" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Show at most 9 media items in each timeline post"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "每条时间轴动态最多显示 9 项媒体"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "每則時間軸貼文最多顯示 9 項媒體"
+ }
+ }
+ }
+ },
"appearance_media_group_subtitle" : {
"localizations" : {
"af" : {
@@ -117393,6 +117437,62 @@
}
}
},
+ "post_action_fixed_width" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fixed button width"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ボタンの幅を固定"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "按钮固定宽度"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "按鈕固定寬度"
+ }
+ }
+ }
+ },
+ "post_action_fixed_width_description" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Keep action buttons with counts at a consistent width"
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "カウント付きアクションボタンの幅を揃えます"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "让带数字的操作按钮保持统一宽度"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "讓帶數字的操作按鈕保持一致寬度"
+ }
+ }
+ }
+ },
"post_action_layout_customize_description" : {
"localizations" : {
"af" : {
@@ -159460,4 +159560,4 @@
}
},
"version" : "1.1"
-}
\ No newline at end of file
+}
diff --git a/appleApp/ios/UI/Component/Status/StatusShareSheet.swift b/appleApp/ios/UI/Component/Status/StatusShareSheet.swift
index 226a249b48..f62c608e2f 100644
--- a/appleApp/ios/UI/Component/Status/StatusShareSheet.swift
+++ b/appleApp/ios/UI/Component/Status/StatusShareSheet.swift
@@ -189,12 +189,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: true,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: .never,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/ios/UI/Component/Status/StatusUIKitActions.swift b/appleApp/ios/UI/Component/Status/StatusUIKitActions.swift
index f9b71f8adb..145f995d5e 100644
--- a/appleApp/ios/UI/Component/Status/StatusUIKitActions.swift
+++ b/appleApp/ios/UI/Component/Status/StatusUIKitActions.swift
@@ -18,6 +18,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
private var allowSpacer: Bool = true
private var postActionStyle: PostActionStyle = .leftAligned
private var postActionLayout: PostActionLayoutConfig = PostActionLayoutConfig.companion.Default
+ private var postActionFixedWidth: Bool = true
private var showNumbers: Bool = true
private var fontSize: CGFloat = 13
private var textStyle: UIFont.TextStyle = .footnote
@@ -43,6 +44,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
allowSpacer: Bool = true,
postActionStyle: PostActionStyle,
postActionLayout: PostActionLayoutConfig = PostActionLayoutConfig.companion.Default,
+ postActionFixedWidth: Bool = true,
applyPostActionLayout: Bool = true,
showNumbers: Bool,
isDetail: Bool
@@ -54,6 +56,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
self.allowSpacer = allowSpacer
self.postActionStyle = postActionStyle
self.postActionLayout = postActionLayout
+ self.postActionFixedWidth = postActionFixedWidth
self.showNumbers = showNumbers
self.fontSize = UIFontMetrics(forTextStyle: .footnote).scaledValue(for: 13)
self.textStyle = isDetail ? .body : .footnote
@@ -65,7 +68,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
}
private var actionIconSize: CGFloat {
- max(actionFont.pointSize + 1, fontSize * 1.2)
+ actionFont.pointSize
}
private func rebuild() {
@@ -92,7 +95,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
if postActionStyle == .stretch, allowSpacer {
isStretch = true
for (index, item) in data.enumerated() {
- if let v = makeActionView(for: item, isFixedWidth: index != data.count - 1) {
+ if let v = makeActionView(for: item, isFixedWidth: postActionFixedWidth && index != data.count - 1) {
desired.append(v)
}
}
@@ -111,7 +114,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
spacerIndex = desired.count
}
- if let v = makeActionView(for: item, isFixedWidth: index != data.count - 1) {
+ if let v = makeActionView(for: item, isFixedWidth: postActionFixedWidth && index != data.count - 1) {
desired.append(v)
}
}
@@ -150,7 +153,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
}
// Compute total content width
- let spacing: CGFloat = 8
+ let spacing: CGFloat = 4
var totalContentWidth: CGFloat = 0
var childSizes: [CGSize] = []
for child in managedChildren {
@@ -286,7 +289,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
let title: String?
if useText, let text = item.text?.resolvedString {
title = text
- } else if showNumbers, let count = item.count?.humanized {
+ } else if showNumbers, let count = item.count?.humanized, !count.isEmpty {
title = count
} else {
title = nil
@@ -302,8 +305,9 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
tintColor: item.color?.uiColor ?? .secondaryLabel,
font: actionFont,
iconSize: actionIconSize,
- minimumTextWidth: isFixedWidth && title != nil ? fontSize * 2.5 : nil,
- minimumIconOnlySize: nil
+ minimumTextWidth: isFixedWidth && showNumbers && item.count != nil ? fontSize * 2.5 : nil,
+ minimumIconOnlySize: nil,
+ usesExpandedHitArea: !useText
) { [weak self] in
guard let self = self else { return }
let gen = UIImpactFeedbackGenerator(style: .medium)
@@ -315,7 +319,7 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
private func makeGroupButton(group: ActionMenu.Group, isFixedWidth: Bool) -> UIView {
let title: String?
- if let text = group.displayItem.count?.humanized, showNumbers {
+ if let text = group.displayItem.count?.humanized, showNumbers, !text.isEmpty {
title = text
} else {
title = nil
@@ -331,8 +335,9 @@ final class StatusActionsUIView: UIView, ManualLayoutMeasurable, TimelineHeightP
tintColor: group.displayItem.color?.uiColor ?? .secondaryLabel,
font: actionFont,
iconSize: actionIconSize,
- minimumTextWidth: isFixedWidth && title != nil ? fontSize * 2.5 : nil,
- minimumIconOnlySize: title == nil ? actionIconSize : nil
+ minimumTextWidth: isFixedWidth && showNumbers && group.displayItem.count != nil ? fontSize * 2.5 : nil,
+ minimumIconOnlySize: title == nil ? fontSize + 2 : nil,
+ usesExpandedHitArea: !useText
)
control.showsMenuAsPrimaryAction = true
control.menu = UIMenu(children: [
@@ -506,6 +511,7 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
private var minimumTextWidth: CGFloat?
private var iconSize: CGFloat = 0
private var currentSpacing: CGFloat = 0
+ private var horizontalInset: CGFloat = 0
private var onTap: (() -> Void)?
override init(frame: CGRect) {
@@ -520,7 +526,8 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
font: UIFont,
iconSize: CGFloat,
minimumTextWidth: CGFloat?,
- minimumIconOnlySize: CGFloat?
+ minimumIconOnlySize: CGFloat?,
+ usesExpandedHitArea: Bool
) {
self.init(frame: .zero)
configure(
@@ -531,6 +538,7 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
iconSize: iconSize,
minimumTextWidth: minimumTextWidth,
minimumIconOnlySize: minimumIconOnlySize,
+ usesExpandedHitArea: usesExpandedHitArea,
onTap: nil
)
}
@@ -565,6 +573,7 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
iconSize: CGFloat,
minimumTextWidth: CGFloat?,
minimumIconOnlySize: CGFloat?,
+ usesExpandedHitArea: Bool,
onTap: (() -> Void)? = nil
) {
self.minimumIconOnlySize = minimumIconOnlySize
@@ -572,7 +581,8 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
self.iconSize = iconSize
self.onTap = onTap
self.tintColor = tintColor
- self.currentSpacing = title == nil ? 0 : 4
+ self.currentSpacing = title != nil || minimumTextWidth != nil ? 4 : 0
+ self.horizontalInset = usesExpandedHitArea ? 6 : 0
menu = nil
showsMenuAsPrimaryAction = false
@@ -599,6 +609,7 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
label.text = nil
minimumTextWidth = nil
minimumIconOnlySize = nil
+ horizontalInset = 0
}
@objc private func onTapped() {
@@ -608,15 +619,16 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
override func layoutSubviews() {
super.layoutSubviews()
let size = actionContentSize()
- var x: CGFloat = 0
+ let iconLayoutSize = resolvedIconLayoutSize()
+ var x = horizontalInset
if !iconView.isHidden {
iconView.frame = CGRect(
x: x,
- y: (size.height - iconSize) / 2,
- width: iconSize,
- height: iconSize
+ y: (size.height - iconLayoutSize.height) / 2,
+ width: iconLayoutSize.width,
+ height: iconLayoutSize.height
)
- x += iconSize + currentSpacing
+ x += iconLayoutSize.width + currentSpacing
}
if !label.isHidden {
let labelSize = label.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: size.height))
@@ -655,25 +667,40 @@ private final class ActionItemControl: UIButton, ManualLayoutMeasurable, Timelin
}
private func actionContentSize() -> CGSize {
- let minimum = minimumIconOnlySize ?? 0
+ let minimumIconOnlySize = minimumIconOnlySize ?? 0
var w: CGFloat = 0
var h: CGFloat = 0
if !iconView.isHidden {
- w += iconSize
- h = max(h, iconSize)
+ let iconLayoutSize = resolvedIconLayoutSize()
+ w += iconLayoutSize.width
+ h = max(h, iconLayoutSize.height)
}
if !label.isHidden {
let labelSize = label.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
let labelW = max(labelSize.width, minimumTextWidth ?? 0)
w += currentSpacing + labelW
h = max(h, labelSize.height)
+ } else if let minimumTextWidth {
+ w += currentSpacing + minimumTextWidth
}
return CGSize(
- width: ceil(max(w, minimum)),
- height: ceil(max(h, minimum))
+ width: ceil(max(w, minimumIconOnlySize) + horizontalInset * 2),
+ height: ceil(max(h, minimumIconOnlySize))
)
}
+ private func resolvedIconLayoutSize() -> CGSize {
+ let intrinsicSize = iconView.intrinsicContentSize
+ guard intrinsicSize.width > 0,
+ intrinsicSize.height > 0,
+ intrinsicSize.width.isFinite,
+ intrinsicSize.height.isFinite
+ else {
+ return CGSize(width: iconSize, height: iconSize)
+ }
+ return intrinsicSize
+ }
+
override var isHighlighted: Bool {
didSet { alpha = isHighlighted ? 0.55 : 1 }
}
diff --git a/appleApp/ios/UI/Component/Status/StatusUIKitMedia.swift b/appleApp/ios/UI/Component/Status/StatusUIKitMedia.swift
index e36d16567c..7fb5e65e81 100644
--- a/appleApp/ios/UI/Component/Status/StatusUIKitMedia.swift
+++ b/appleApp/ios/UI/Component/Status/StatusUIKitMedia.swift
@@ -297,6 +297,7 @@ final class StatusMediaUIView: UIView, TimelineHeightProviding {
private var cornerRadius: CGFloat = 16
private var isBlurred: Bool = false
private var singleFollowsImageAspect: Bool = true
+ private var limitMediaGridToNine: Bool = true
private var toggleButtonPositionConstraints: [NSLayoutConstraint] = []
private var aspectConstraint: NSLayoutConstraint?
private var lastLayoutWidth: CGFloat = 0
@@ -309,17 +310,20 @@ final class StatusMediaUIView: UIView, TimelineHeightProviding {
let sensitive: Bool
let cornerRadius: CGFloat
let singleFollowsImageAspect: Bool
+ let limitMediaGridToNine: Bool
init(
data: [UiMedia],
sensitive: Bool,
cornerRadius: CGFloat,
- singleFollowsImageAspect: Bool
+ singleFollowsImageAspect: Bool,
+ limitMediaGridToNine: Bool
) {
items = data.map(MediaItemSignature.init)
self.sensitive = sensitive
self.cornerRadius = cornerRadius
self.singleFollowsImageAspect = singleFollowsImageAspect
+ self.limitMediaGridToNine = limitMediaGridToNine
}
}
@@ -397,12 +401,19 @@ final class StatusMediaUIView: UIView, TimelineHeightProviding {
return ceil(gridHeight(for: width))
}
- func configure(data: [UiMedia], sensitive: Bool, cornerRadius: CGFloat, singleFollowsImageAspect: Bool) {
+ func configure(
+ data: [UiMedia],
+ sensitive: Bool,
+ cornerRadius: CGFloat,
+ singleFollowsImageAspect: Bool,
+ limitMediaGridToNine: Bool
+ ) {
let signature = ConfigureSignature(
data: data,
sensitive: sensitive,
cornerRadius: cornerRadius,
- singleFollowsImageAspect: singleFollowsImageAspect
+ singleFollowsImageAspect: singleFollowsImageAspect,
+ limitMediaGridToNine: limitMediaGridToNine
)
guard lastConfigureSignature != signature else { return }
let shouldResetBlur =
@@ -413,6 +424,7 @@ final class StatusMediaUIView: UIView, TimelineHeightProviding {
self.sensitive = sensitive
self.cornerRadius = cornerRadius
self.singleFollowsImageAspect = singleFollowsImageAspect
+ self.limitMediaGridToNine = limitMediaGridToNine
if shouldResetBlur {
self.isBlurred = sensitive
}
@@ -643,7 +655,7 @@ final class StatusMediaUIView: UIView, TimelineHeightProviding {
}
private var visibleItemCount: Int {
- min(items.count, Self.maxVisibleMediaCount)
+ limitMediaGridToNine ? min(items.count, Self.maxVisibleMediaCount) : items.count
}
private var overflowCount: Int {
@@ -920,6 +932,7 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
private var appearanceShowMedia: Bool = true
private var appearanceShowSensitive: Bool = false
private var appearanceExpandMediaSize: Bool = true
+ private var appearanceLimitMediaGridToNine: Bool = true
private var showButtonConstraints: [NSLayoutConstraint] = []
private var lastConfigureSignature: ConfigureSignature?
@@ -930,6 +943,7 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
let appearanceShowMedia: Bool
let appearanceShowSensitive: Bool
let appearanceExpandMediaSize: Bool
+ let appearanceLimitMediaGridToNine: Bool
init(
data: [UiMedia],
@@ -937,7 +951,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
cornerRadius: CGFloat,
appearanceShowMedia: Bool,
appearanceShowSensitive: Bool,
- appearanceExpandMediaSize: Bool
+ appearanceExpandMediaSize: Bool,
+ appearanceLimitMediaGridToNine: Bool
) {
items = data.map(MediaItemSignature.init)
self.sensitive = sensitive
@@ -945,6 +960,7 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
self.appearanceShowMedia = appearanceShowMedia
self.appearanceShowSensitive = appearanceShowSensitive
self.appearanceExpandMediaSize = appearanceExpandMediaSize
+ self.appearanceLimitMediaGridToNine = appearanceLimitMediaGridToNine
}
}
@@ -1024,7 +1040,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
cornerRadius: CGFloat,
appearanceShowMedia: Bool,
appearanceShowSensitive: Bool,
- appearanceExpandMediaSize: Bool
+ appearanceExpandMediaSize: Bool,
+ appearanceLimitMediaGridToNine: Bool
) {
let signature = ConfigureSignature(
data: data,
@@ -1032,7 +1049,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
cornerRadius: cornerRadius,
appearanceShowMedia: appearanceShowMedia,
appearanceShowSensitive: appearanceShowSensitive,
- appearanceExpandMediaSize: appearanceExpandMediaSize
+ appearanceExpandMediaSize: appearanceExpandMediaSize,
+ appearanceLimitMediaGridToNine: appearanceLimitMediaGridToNine
)
guard lastConfigureSignature != signature else { return }
let shouldResetExpanded =
@@ -1045,6 +1063,7 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
self.appearanceShowMedia = appearanceShowMedia
self.appearanceShowSensitive = appearanceShowSensitive
self.appearanceExpandMediaSize = appearanceExpandMediaSize
+ self.appearanceLimitMediaGridToNine = appearanceLimitMediaGridToNine
if shouldResetExpanded {
self.expanded = false
}
@@ -1061,7 +1080,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
data: [],
sensitive: false,
cornerRadius: cornerRadius,
- singleFollowsImageAspect: appearanceExpandMediaSize
+ singleFollowsImageAspect: appearanceExpandMediaSize,
+ limitMediaGridToNine: appearanceLimitMediaGridToNine
)
grid.performDeferredPoolCleanup()
grid.isHidden = true
@@ -1092,7 +1112,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
data: items,
sensitive: !appearanceShowSensitive && sensitive,
cornerRadius: cornerRadius,
- singleFollowsImageAspect: appearanceExpandMediaSize
+ singleFollowsImageAspect: appearanceExpandMediaSize,
+ limitMediaGridToNine: appearanceLimitMediaGridToNine
)
} else {
grid.isHidden = true
@@ -1102,7 +1123,8 @@ final class StatusMediaContentUIView: UIView, TimelineHeightProviding {
data: [],
sensitive: false,
cornerRadius: cornerRadius,
- singleFollowsImageAspect: appearanceExpandMediaSize
+ singleFollowsImageAspect: appearanceExpandMediaSize,
+ limitMediaGridToNine: appearanceLimitMediaGridToNine
)
}
setNeedsLayout()
diff --git a/appleApp/ios/UI/Component/Status/StatusUIKitView.swift b/appleApp/ios/UI/Component/Status/StatusUIKitView.swift
index 07007d959a..1376b07e2f 100644
--- a/appleApp/ios/UI/Component/Status/StatusUIKitView.swift
+++ b/appleApp/ios/UI/Component/Status/StatusUIKitView.swift
@@ -792,7 +792,8 @@ final class StatusUIKitView: UIView, UIGestureRecognizerDelegate, ManualLayoutMe
cornerRadius: corner,
appearanceShowMedia: appearance.showMedia,
appearanceShowSensitive: appearance.showSensitiveContent,
- appearanceExpandMediaSize: appearance.expandMediaSize
+ appearanceExpandMediaSize: appearance.expandMediaSize,
+ appearanceLimitMediaGridToNine: appearance.limitMediaGridToNine
)
mediaView.onMediaClicked = { [weak self] media, index in
guard let self else { return }
@@ -892,6 +893,7 @@ final class StatusUIKitView: UIView, UIGestureRecognizerDelegate, ManualLayoutMe
allowSpacer: true,
postActionStyle: appearance.postActionStyle,
postActionLayout: appearance.postActionLayout,
+ postActionFixedWidth: appearance.postActionFixedWidth,
showNumbers: appearance.showNumbers,
isDetail: isDetail
)
diff --git a/appleApp/ios/UI/Component/TimelinePresentationEditor.swift b/appleApp/ios/UI/Component/TimelinePresentationEditor.swift
index 5cb8543b40..0d8e79dd0c 100644
--- a/appleApp/ios/UI/Component/TimelinePresentationEditor.swift
+++ b/appleApp/ios/UI/Component/TimelinePresentationEditor.swift
@@ -387,6 +387,17 @@ private struct LayoutAppearanceOverrideGroup: View {
Text("appearance_show_numbers")
Text("appearance_show_numbers_description")
}
+ Toggle(isOn: Binding(get: {
+ timelineAppearance.postActionFixedWidth
+ }, set: { value in
+ appearancePatch = TimelinePresentationAppearancePatchHelper.shared.setPostActionFixedWidth(
+ patch: appearancePatch,
+ value: value
+ )
+ })) {
+ Text("post_action_fixed_width")
+ Text("post_action_fixed_width_description")
+ }
}
}
}
@@ -513,6 +524,17 @@ private struct MediaAppearanceOverrideGroup: View {
Text("appearance_expand_media_size")
Text("appearance_expand_media_size_description")
}
+ Toggle(isOn: Binding(get: {
+ timelineAppearance.limitMediaGridToNine
+ }, set: { value in
+ appearancePatch = TimelinePresentationAppearancePatchHelper.shared.setLimitMediaGridToNine(
+ patch: appearancePatch,
+ value: value
+ )
+ })) {
+ Text("appearance_limit_media_grid_to_nine")
+ Text("appearance_limit_media_grid_to_nine_description")
+ }
Toggle(isOn: Binding(get: {
timelineAppearance.showSensitiveContent
}, set: { value in
diff --git a/appleApp/ios/UI/Component/UIKitAppearance.swift b/appleApp/ios/UI/Component/UIKitAppearance.swift
index e2658f4305..22d1220eb5 100644
--- a/appleApp/ios/UI/Component/UIKitAppearance.swift
+++ b/appleApp/ios/UI/Component/UIKitAppearance.swift
@@ -33,6 +33,7 @@ struct StatusUIKitAppearance: Equatable {
let postActionStyleID: String
let postActionLayout: PostActionLayoutConfig
let postActionLayoutID: String
+ let postActionFixedWidth: Bool
let showNumbers: Bool
let showMedia: Bool
let showSensitiveContent: Bool
@@ -40,6 +41,7 @@ struct StatusUIKitAppearance: Equatable {
let showLinkPreview: Bool
let compatLinkPreview: Bool
let expandMediaSize: Bool
+ let limitMediaGridToNine: Bool
let aiAgentEnabled: Bool
init(timeline: TimelineAppearance, fontSizeDiff: Float = 0) {
@@ -54,6 +56,7 @@ struct StatusUIKitAppearance: Equatable {
postActionStyleID = timeline.postActionStyle.name
postActionLayout = timeline.postActionLayout
postActionLayoutID = PostActionLayoutHelpers.shared.signature(config: timeline.postActionLayout)
+ postActionFixedWidth = timeline.postActionFixedWidth
showNumbers = timeline.showNumbers
showMedia = timeline.showMedia
showSensitiveContent = timeline.showSensitiveContent
@@ -61,6 +64,7 @@ struct StatusUIKitAppearance: Equatable {
showLinkPreview = timeline.showLinkPreview
compatLinkPreview = timeline.compatLinkPreview
expandMediaSize = timeline.expandMediaSize
+ limitMediaGridToNine = timeline.limitMediaGridToNine
aiAgentEnabled = timeline.aiConfig.agent
}
@@ -72,6 +76,7 @@ struct StatusUIKitAppearance: Equatable {
lhs.absoluteTimestamp == rhs.absoluteTimestamp &&
lhs.postActionStyleID == rhs.postActionStyleID &&
lhs.postActionLayoutID == rhs.postActionLayoutID &&
+ lhs.postActionFixedWidth == rhs.postActionFixedWidth &&
lhs.showNumbers == rhs.showNumbers &&
lhs.showMedia == rhs.showMedia &&
lhs.showSensitiveContent == rhs.showSensitiveContent &&
@@ -79,6 +84,7 @@ struct StatusUIKitAppearance: Equatable {
lhs.showLinkPreview == rhs.showLinkPreview &&
lhs.compatLinkPreview == rhs.compatLinkPreview &&
lhs.expandMediaSize == rhs.expandMediaSize &&
+ lhs.limitMediaGridToNine == rhs.limitMediaGridToNine &&
lhs.aiAgentEnabled == rhs.aiAgentEnabled
}
}
diff --git a/appleApp/ios/UI/FlareTheme.swift b/appleApp/ios/UI/FlareTheme.swift
index 21585be900..5e9b4efae2 100644
--- a/appleApp/ios/UI/FlareTheme.swift
+++ b/appleApp/ios/UI/FlareTheme.swift
@@ -65,12 +65,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: expandContentWarning,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: videoAutoplay,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/ios/UI/Screen/GalleryDetailScreen.swift b/appleApp/ios/UI/Screen/GalleryDetailScreen.swift
index fe42c02340..1932225e4b 100644
--- a/appleApp/ios/UI/Screen/GalleryDetailScreen.swift
+++ b/appleApp/ios/UI/Screen/GalleryDetailScreen.swift
@@ -777,12 +777,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: expandContentWarning,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: videoAutoplay,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/ios/UI/Screen/PostActionLayoutScreen.swift b/appleApp/ios/UI/Screen/PostActionLayoutScreen.swift
index c8baecbd68..d91f533b0a 100644
--- a/appleApp/ios/UI/Screen/PostActionLayoutScreen.swift
+++ b/appleApp/ios/UI/Screen/PostActionLayoutScreen.swift
@@ -228,12 +228,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: expandContentWarning,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: videoAutoplay,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: config,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/appleApp/macos/App/MacSidebarAppearanceOverrideGroups.swift b/appleApp/macos/App/MacSidebarAppearanceOverrideGroups.swift
index c57f25a9c9..3e248eea7d 100644
--- a/appleApp/macos/App/MacSidebarAppearanceOverrideGroups.swift
+++ b/appleApp/macos/App/MacSidebarAppearanceOverrideGroups.swift
@@ -79,6 +79,17 @@ struct MacSidebarLayoutAppearanceOverrideGroup: View {
})) {
Text("appearance_show_numbers")
}
+
+ Toggle(isOn: Binding(get: {
+ timelineAppearance.postActionFixedWidth
+ }, set: { value in
+ appearancePatch = TimelinePresentationAppearancePatchHelper.shared.setPostActionFixedWidth(
+ patch: appearancePatch,
+ value: value
+ )
+ })) {
+ Text("post_action_fixed_width")
+ }
}
}
}
@@ -204,6 +215,17 @@ struct MacSidebarMediaAppearanceOverrideGroup: View {
Text("appearance_expand_media_size")
}
+ Toggle(isOn: Binding(get: {
+ timelineAppearance.limitMediaGridToNine
+ }, set: { value in
+ appearancePatch = TimelinePresentationAppearancePatchHelper.shared.setLimitMediaGridToNine(
+ patch: appearancePatch,
+ value: value
+ )
+ })) {
+ Text("appearance_limit_media_grid_to_nine")
+ }
+
Toggle(isOn: Binding(get: {
timelineAppearance.showSensitiveContent
}, set: { value in
diff --git a/appleApp/macos/App/RootView.swift b/appleApp/macos/App/RootView.swift
index d2d21c65fb..ef2fdc9fe6 100644
--- a/appleApp/macos/App/RootView.swift
+++ b/appleApp/macos/App/RootView.swift
@@ -171,7 +171,7 @@ struct RootView: View {
} detail: {
if let selectedTab {
Router(
- initialRoute: selectedTab,
+ initialRoute: routeWithLatestTimelinePresentation(selectedTab),
externalNavigationRequest: mainNavigationRequest
)
.navigationSplitViewColumnWidth(min: 280, ideal: 400, max: 500)
@@ -257,6 +257,18 @@ struct RootView: View {
}
}
+ private func routeWithLatestTimelinePresentation(_ route: Route) -> Route {
+ guard case .timeline(let selectedTimeline) = route,
+ case .success(let data) = onEnum(of: homeTimelineWithTabsPresenter.state.tabState),
+ let latestTimeline = data.data
+ .cast(UiTimelineTabItem.self)
+ .first(where: { $0.id == selectedTimeline.id })
+ else {
+ return route
+ }
+ return .timeline(latestTimeline)
+ }
+
private func handleMainWindowNavigationRequest(_ request: MacMainWindowNavigationRequest?) {
guard let request else {
return
diff --git a/appleApp/macos/UI/FlareTheme.swift b/appleApp/macos/UI/FlareTheme.swift
index 6a2357750c..5bcff3133f 100644
--- a/appleApp/macos/UI/FlareTheme.swift
+++ b/appleApp/macos/UI/FlareTheme.swift
@@ -49,12 +49,14 @@ private extension TimelineAppearance {
showSensitiveContent: showSensitiveContent,
expandContentWarning: expandContentWarning,
expandMediaSize: expandMediaSize,
+ limitMediaGridToNine: limitMediaGridToNine,
videoAutoplay: videoAutoplay,
showLinkPreview: showLinkPreview,
compatLinkPreview: compatLinkPreview,
showNumbers: showNumbers,
postActionStyle: postActionStyle,
postActionLayout: postActionLayout,
+ postActionFixedWidth: postActionFixedWidth,
fullWidthPost: fullWidthPost,
absoluteTimestamp: absoluteTimestamp,
showPlatformLogo: showPlatformLogo,
diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/CommonStatusComponent.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/CommonStatusComponent.kt
index 96c4644166..b3b2e9cace 100644
--- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/CommonStatusComponent.kt
+++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/CommonStatusComponent.kt
@@ -910,12 +910,11 @@ internal fun StatusActions(
verticalAlignment = Alignment.Bottom,
horizontalArrangement =
when (appearanceSettings.postActionStyle) {
- PostActionStyle.Hidden -> Arrangement.Start
- PostActionStyle.LeftAligned -> Arrangement.Start
- PostActionStyle.RightAligned -> Arrangement.End
+ PostActionStyle.Hidden -> Arrangement.spacedBy(4.dp, Alignment.Start)
+ PostActionStyle.LeftAligned -> Arrangement.spacedBy(4.dp, Alignment.Start)
+ PostActionStyle.RightAligned -> Arrangement.spacedBy(4.dp, Alignment.End)
PostActionStyle.Stretch -> Arrangement.SpaceBetween
},
-// horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
displayItems.fastForEachIndexed { index, action ->
if (index == displayItems.lastIndex && appearanceSettings.postActionStyle == PostActionStyle.LeftAligned) {
@@ -931,7 +930,10 @@ internal fun StatusActions(
color =
action.displayItem.color?.toComposeColor()
?: PlatformContentColor.current,
- withTextMinWidth = action.displayItem.count != null && index != displayItems.lastIndex,
+ withTextMinWidth =
+ appearanceSettings.postActionFixedWidth &&
+ action.displayItem.count != null &&
+ index != displayItems.lastIndex,
) { closeMenu, isMenuShown ->
action.actions.fastForEach { subActions ->
when (subActions) {
@@ -958,7 +960,10 @@ internal fun StatusActions(
// Fallback or handle null
number = action.count,
color = action.color?.toComposeColor() ?: PlatformContentColor.current,
- withTextMinWidth = action.count != null && index != displayItems.lastIndex,
+ withTextMinWidth =
+ appearanceSettings.postActionFixedWidth &&
+ action.count != null &&
+ index != displayItems.lastIndex,
onClicked = {
action.onClicked.let { onClick ->
haptics.performHapticFeedback(HapticFeedbackType.ContextClick)
diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusActionButton.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusActionButton.kt
index 0871e67cce..d6f4bd3a06 100644
--- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusActionButton.kt
+++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusActionButton.kt
@@ -14,10 +14,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
@@ -56,13 +54,11 @@ public fun StatusActionButton(
) {
val interactionSource = remember { MutableInteractionSource() }
val appearanceSettings = LocalTimelineAppearance.current
- Row(
- modifier =
- modifier
- .padding(vertical = 4.dp, horizontal = 4.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(2.dp),
- ) {
+ val displayNumber =
+ number?.takeIf {
+ appearanceSettings.showNumbers && it.humanized.isNotEmpty()
+ }
+ val actionIcon: @Composable () -> Unit = {
if (!LocalIsScrollingInProgress.current) {
val contentColor = PlatformContentColor.current
AnimatedContent(
@@ -125,35 +121,60 @@ public fun StatusActionButton(
tint = color,
)
}
- if (withTextMinWidth || (number != null && appearanceSettings.showNumbers)) {
+ }
+ Row(
+ modifier =
+ modifier
+ .padding(vertical = 4.dp, horizontal = 4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ if (withTextMinWidth && displayNumber == null) {
Box(
- modifier = Modifier.align(Alignment.CenterVertically),
+ contentAlignment = Alignment.CenterStart,
) {
- if (withTextMinWidth) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ FAIcon(
+ imageVector = icon,
+ contentDescription = null,
+ modifier = Modifier.height(PlatformTextStyle.current.fontSize.value.dp + 2.dp),
+ tint = Color.Transparent,
+ )
PlatformText(
"0000",
color = Color.Transparent,
)
}
- if (number != null && appearanceSettings.showNumbers) {
- AnimatedNumber(
- number = number,
- color = color,
- modifier =
- Modifier
- .pointerHoverIcon(PointerIcon.Hand)
- .clickable(
- onClick = onClicked,
- enabled = enabled,
- interactionSource = interactionSource,
- indication = null,
- ),
+ actionIcon()
+ }
+ } else {
+ actionIcon()
+ }
+ if (displayNumber != null) {
+ Box(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ ) {
+ if (withTextMinWidth) {
+ PlatformText(
+ "0000",
+ color = Color.Transparent,
)
- } else {
- if (withTextMinWidth) {
- Spacer(modifier = Modifier.width(4.dp))
- }
}
+ AnimatedNumber(
+ number = displayNumber,
+ color = color,
+ modifier =
+ Modifier
+ .pointerHoverIcon(PointerIcon.Hand)
+ .clickable(
+ onClick = onClicked,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ indication = null,
+ ),
+ )
}
}
}
diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusMediaComponent.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusMediaComponent.kt
index c749be5914..6d588f5408 100644
--- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusMediaComponent.kt
+++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusMediaComponent.kt
@@ -206,6 +206,7 @@ internal fun StatusMediaComponent(
}
}
},
+ maxItems = if (appearanceSettings.limitMediaGridToNine) 9 else data.size,
modifier =
Modifier
.let {
diff --git a/desktopApp/src/main/composeResources/values-zh-rCN/strings.xml b/desktopApp/src/main/composeResources/values-zh-rCN/strings.xml
index 5d27b200b3..9192c93fbd 100644
--- a/desktopApp/src/main/composeResources/values-zh-rCN/strings.xml
+++ b/desktopApp/src/main/composeResources/values-zh-rCN/strings.xml
@@ -290,6 +290,8 @@
总是显示内容警告隐藏的帖子内容
保持媒体比例
在时间轴中按原比例显示媒体
+ 媒体网格最多显示 9 项
+ 每条时间轴动态最多显示 9 项媒体
视频自动播放
自动播放内容中的视频
仅 Wi-Fi
@@ -334,6 +336,8 @@
动态操作
自定义操作
选择哪些操作显示在按钮行、更多菜单或保持隐藏
+ 按钮固定宽度
+ 让带数字的操作按钮保持统一宽度
按钮行
更多菜单
隐藏
diff --git a/desktopApp/src/main/composeResources/values/strings.xml b/desktopApp/src/main/composeResources/values/strings.xml
index ea181e110a..53072e6a59 100644
--- a/desktopApp/src/main/composeResources/values/strings.xml
+++ b/desktopApp/src/main/composeResources/values/strings.xml
@@ -326,6 +326,8 @@
Always reveal post content hidden by content warnings
Preserve media aspect ratio
Show media at its original aspect ratio in timelines
+ Limit media grids to 9 items
+ Show at most 9 media items in each timeline post
Video autoplay
Automatically play videos in posts
Wi-Fi only
@@ -370,6 +372,8 @@
Post actions
Customize actions
Choose which actions appear in the row, More menu, or stay hidden
+ Fixed button width
+ Keep action buttons with counts at a consistent width
Button row
More menu
Hidden
diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/TimelinePresentationEditor.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/TimelinePresentationEditor.kt
index 8bea753bc5..b95a8d9b0c 100644
--- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/TimelinePresentationEditor.kt
+++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/TimelinePresentationEditor.kt
@@ -61,6 +61,8 @@ import dev.dimension.flare.settings_appearance_full_width_post
import dev.dimension.flare.settings_appearance_full_width_post_description
import dev.dimension.flare.settings_appearance_layout_group_subtitle
import dev.dimension.flare.settings_appearance_layout_group_title
+import dev.dimension.flare.settings_appearance_limit_media_grid_to_nine
+import dev.dimension.flare.settings_appearance_limit_media_grid_to_nine_description
import dev.dimension.flare.settings_appearance_media_group_subtitle
import dev.dimension.flare.settings_appearance_media_group_title
import dev.dimension.flare.settings_appearance_post_action_style
@@ -91,6 +93,8 @@ import dev.dimension.flare.settings_appearance_video_autoplay_always
import dev.dimension.flare.settings_appearance_video_autoplay_description
import dev.dimension.flare.settings_appearance_video_autoplay_never
import dev.dimension.flare.settings_appearance_video_autoplay_wifi
+import dev.dimension.flare.settings_post_action_fixed_width
+import dev.dimension.flare.settings_post_action_fixed_width_description
import dev.dimension.flare.tab_settings_filter_content_group
import dev.dimension.flare.tab_settings_filter_desc
import dev.dimension.flare.tab_settings_filter_image
@@ -152,6 +156,7 @@ internal fun TimelinePresentationEditor(
appearancePatch.contains(AppearanceKeys.TimelineDisplayMode) ||
appearancePatch.contains(AppearanceKeys.FullWidthPost) ||
appearancePatch.contains(AppearanceKeys.PostActionStyle) ||
+ appearancePatch.contains(AppearanceKeys.PostActionFixedWidth) ||
appearancePatch.contains(AppearanceKeys.ShowNumbers)
val displayOverridesEnabled =
appearancePatch.contains(AppearanceKeys.AbsoluteTimestamp) ||
@@ -163,6 +168,7 @@ internal fun TimelinePresentationEditor(
appearancePatch.contains(AppearanceKeys.ShowSensitiveContent) ||
appearancePatch.contains(AppearanceKeys.ExpandContentWarning) ||
appearancePatch.contains(AppearanceKeys.ExpandMediaSize) ||
+ appearancePatch.contains(AppearanceKeys.LimitMediaGridToNine) ||
appearancePatch.contains(AppearanceKeys.VideoAutoplay)
val themeOverridesEnabled = appearancePatch.contains(AppearanceKeys.AvatarShape)
var showFilterDialog by remember { mutableStateOf(false) }
@@ -229,11 +235,16 @@ internal fun TimelinePresentationEditor(
AppearanceKeys.PostActionStyle,
timelineAppearance.postActionStyle,
).set(AppearanceKeys.ShowNumbers, timelineAppearance.showNumbers)
+ .set(
+ AppearanceKeys.PostActionFixedWidth,
+ timelineAppearance.postActionFixedWidth,
+ )
} else {
appearancePatch.clearAll(
AppearanceKeys.TimelineDisplayMode,
AppearanceKeys.FullWidthPost,
AppearanceKeys.PostActionStyle,
+ AppearanceKeys.PostActionFixedWidth,
AppearanceKeys.ShowNumbers,
)
},
@@ -310,6 +321,20 @@ internal fun TimelinePresentationEditor(
)
},
)
+ ExpanderItemSeparator()
+ SwitchItem(
+ title = stringResource(Res.string.settings_post_action_fixed_width),
+ caption = stringResource(Res.string.settings_post_action_fixed_width_description),
+ checked = timelineAppearance.postActionFixedWidth,
+ onCheckedChange = {
+ onAppearancePatchChange(
+ appearancePatch.set(
+ AppearanceKeys.PostActionFixedWidth,
+ it,
+ ),
+ )
+ },
+ )
}
}
}
@@ -425,6 +450,9 @@ internal fun TimelinePresentationEditor(
).set(
AppearanceKeys.ExpandMediaSize,
timelineAppearance.expandMediaSize,
+ ).set(
+ AppearanceKeys.LimitMediaGridToNine,
+ timelineAppearance.limitMediaGridToNine,
).set(
AppearanceKeys.VideoAutoplay,
timelineAppearance.videoAutoplay,
@@ -435,6 +463,7 @@ internal fun TimelinePresentationEditor(
AppearanceKeys.ShowSensitiveContent,
AppearanceKeys.ExpandContentWarning,
AppearanceKeys.ExpandMediaSize,
+ AppearanceKeys.LimitMediaGridToNine,
AppearanceKeys.VideoAutoplay,
)
},
@@ -485,6 +514,20 @@ internal fun TimelinePresentationEditor(
},
)
ExpanderItemSeparator()
+ SwitchItem(
+ title = stringResource(Res.string.settings_appearance_limit_media_grid_to_nine),
+ caption = stringResource(Res.string.settings_appearance_limit_media_grid_to_nine_description),
+ checked = timelineAppearance.limitMediaGridToNine,
+ onCheckedChange = {
+ onAppearancePatchChange(
+ appearancePatch.set(
+ AppearanceKeys.LimitMediaGridToNine,
+ it,
+ ),
+ )
+ },
+ )
+ ExpanderItemSeparator()
SwitchItem(
title = stringResource(Res.string.settings_appearance_expand_media),
caption = stringResource(Res.string.settings_appearance_expand_media_description),
diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt
index 9e8a9d0efc..1a5ca4cae6 100644
--- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt
+++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt
@@ -157,6 +157,8 @@ import dev.dimension.flare.settings_appearance_full_width_post
import dev.dimension.flare.settings_appearance_full_width_post_description
import dev.dimension.flare.settings_appearance_layout_group_subtitle
import dev.dimension.flare.settings_appearance_layout_group_title
+import dev.dimension.flare.settings_appearance_limit_media_grid_to_nine
+import dev.dimension.flare.settings_appearance_limit_media_grid_to_nine_description
import dev.dimension.flare.settings_appearance_media_group_subtitle
import dev.dimension.flare.settings_appearance_media_group_title
import dev.dimension.flare.settings_appearance_post_action_layout
@@ -207,6 +209,8 @@ import dev.dimension.flare.settings_link_open_defaults_title
import dev.dimension.flare.settings_local_history_description
import dev.dimension.flare.settings_local_history_title
import dev.dimension.flare.settings_nostr_relays_manage
+import dev.dimension.flare.settings_post_action_fixed_width
+import dev.dimension.flare.settings_post_action_fixed_width_description
import dev.dimension.flare.settings_privacy_policy
import dev.dimension.flare.settings_rss_management_description
import dev.dimension.flare.settings_rss_management_title
@@ -937,6 +941,24 @@ internal fun SettingsScreen(
)
},
)
+ ExpanderItemSeparator()
+ ExpanderItem(
+ heading = {
+ Text(stringResource(Res.string.settings_post_action_fixed_width))
+ },
+ caption = {
+ Text(stringResource(Res.string.settings_post_action_fixed_width_description))
+ },
+ trailing = {
+ Switcher(
+ checked = LocalTimelineAppearance.current.postActionFixedWidth,
+ {
+ state.appearanceState.update(AppearanceKeys.PostActionFixedWidth, it)
+ },
+ textBefore = true,
+ )
+ },
+ )
}
}
}
@@ -1101,6 +1123,24 @@ internal fun SettingsScreen(
}
AnimatedVisibility(LocalTimelineAppearance.current.showMedia) {
Column {
+ ExpanderItemSeparator()
+ ExpanderItem(
+ heading = {
+ Text(stringResource(Res.string.settings_appearance_limit_media_grid_to_nine))
+ },
+ caption = {
+ Text(stringResource(Res.string.settings_appearance_limit_media_grid_to_nine_description))
+ },
+ trailing = {
+ Switcher(
+ checked = LocalTimelineAppearance.current.limitMediaGridToNine,
+ {
+ state.appearanceState.update(AppearanceKeys.LimitMediaGridToNine, it)
+ },
+ textBefore = true,
+ )
+ },
+ )
ExpanderItemSeparator()
ExpanderItem(
heading = {
diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt b/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt
index 1fe4f64000..4ffebe825e 100644
--- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt
+++ b/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt
@@ -17,6 +17,7 @@ public object TimelinePresentationAppearancePatchHelper {
patch.contains(AppearanceKeys.TimelineDisplayMode) ||
patch.contains(AppearanceKeys.FullWidthPost) ||
patch.contains(AppearanceKeys.PostActionStyle) ||
+ patch.contains(AppearanceKeys.PostActionFixedWidth) ||
patch.contains(AppearanceKeys.ShowNumbers)
public fun displayOverridesEnabled(patch: AppearancePatch): Boolean =
@@ -30,6 +31,7 @@ public object TimelinePresentationAppearancePatchHelper {
patch.contains(AppearanceKeys.ShowSensitiveContent) ||
patch.contains(AppearanceKeys.ExpandContentWarning) ||
patch.contains(AppearanceKeys.ExpandMediaSize) ||
+ patch.contains(AppearanceKeys.LimitMediaGridToNine) ||
patch.contains(AppearanceKeys.VideoAutoplay)
public fun themeOverridesEnabled(patch: AppearancePatch): Boolean = patch.contains(AppearanceKeys.AvatarShape)
@@ -42,6 +44,7 @@ public object TimelinePresentationAppearancePatchHelper {
.set(AppearanceKeys.TimelineDisplayMode, appearance.timelineDisplayMode)
.set(AppearanceKeys.FullWidthPost, appearance.fullWidthPost)
.set(AppearanceKeys.PostActionStyle, appearance.postActionStyle)
+ .set(AppearanceKeys.PostActionFixedWidth, appearance.postActionFixedWidth)
.set(AppearanceKeys.ShowNumbers, appearance.showNumbers)
public fun disableLayoutOverrides(patch: AppearancePatch): AppearancePatch =
@@ -49,6 +52,7 @@ public object TimelinePresentationAppearancePatchHelper {
.clear(AppearanceKeys.TimelineDisplayMode)
.clear(AppearanceKeys.FullWidthPost)
.clear(AppearanceKeys.PostActionStyle)
+ .clear(AppearanceKeys.PostActionFixedWidth)
.clear(AppearanceKeys.ShowNumbers)
public fun enableDisplayOverrides(
@@ -77,6 +81,7 @@ public object TimelinePresentationAppearancePatchHelper {
.set(AppearanceKeys.ShowSensitiveContent, appearance.showSensitiveContent)
.set(AppearanceKeys.ExpandContentWarning, appearance.expandContentWarning)
.set(AppearanceKeys.ExpandMediaSize, appearance.expandMediaSize)
+ .set(AppearanceKeys.LimitMediaGridToNine, appearance.limitMediaGridToNine)
.set(AppearanceKeys.VideoAutoplay, appearance.videoAutoplay)
public fun disableMediaOverrides(patch: AppearancePatch): AppearancePatch =
@@ -85,6 +90,7 @@ public object TimelinePresentationAppearancePatchHelper {
.clear(AppearanceKeys.ShowSensitiveContent)
.clear(AppearanceKeys.ExpandContentWarning)
.clear(AppearanceKeys.ExpandMediaSize)
+ .clear(AppearanceKeys.LimitMediaGridToNine)
.clear(AppearanceKeys.VideoAutoplay)
public fun enableThemeOverrides(
@@ -109,6 +115,11 @@ public object TimelinePresentationAppearancePatchHelper {
value: PostActionStyle,
): AppearancePatch = patch.set(AppearanceKeys.PostActionStyle, value)
+ public fun setPostActionFixedWidth(
+ patch: AppearancePatch,
+ value: Boolean,
+ ): AppearancePatch = patch.set(AppearanceKeys.PostActionFixedWidth, value)
+
public fun setShowNumbers(
patch: AppearancePatch,
value: Boolean,
@@ -154,6 +165,11 @@ public object TimelinePresentationAppearancePatchHelper {
value: Boolean,
): AppearancePatch = patch.set(AppearanceKeys.ExpandMediaSize, value)
+ public fun setLimitMediaGridToNine(
+ patch: AppearancePatch,
+ value: Boolean,
+ ): AppearancePatch = patch.set(AppearanceKeys.LimitMediaGridToNine, value)
+
public fun setVideoAutoplay(
patch: AppearancePatch,
value: VideoAutoplay,
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt
index a3da0a7243..2b0e6ca3bb 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt
@@ -60,6 +60,12 @@ public object AppearanceKeys {
public object ExpandMediaSize : PerTimeline("timeline.expand_media_size", true, Boolean.serializer())
+ public object LimitMediaGridToNine : PerTimeline(
+ "timeline.limit_media_grid_to_nine",
+ true,
+ Boolean.serializer(),
+ )
+
public object VideoAutoplay : PerTimeline(
"timeline.video_autoplay",
AppearanceVideoAutoplay.WIFI,
@@ -84,6 +90,12 @@ public object AppearanceKeys {
AppearancePostActionLayoutConfig.serializer(),
)
+ public object PostActionFixedWidth : PerTimeline(
+ "timeline.post_action_fixed_width",
+ true,
+ Boolean.serializer(),
+ )
+
public object FullWidthPost : PerTimeline("timeline.full_width_post", false, Boolean.serializer())
public object AbsoluteTimestamp : PerTimeline("timeline.absolute_timestamp", false, Boolean.serializer())
@@ -115,12 +127,14 @@ public object AppearanceKeys {
ShowSensitiveContent,
ExpandContentWarning,
ExpandMediaSize,
+ LimitMediaGridToNine,
VideoAutoplay,
ShowLinkPreview,
CompatLinkPreview,
ShowNumbers,
PostActionStyle,
PostActionLayout,
+ PostActionFixedWidth,
FullWidthPost,
AbsoluteTimestamp,
ShowPlatformLogo,
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt
index 31e6f86be9..1c1e9ea04c 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt
@@ -37,12 +37,14 @@ public data class TimelineAppearance(
val showSensitiveContent: Boolean = AppearanceKeys.ShowSensitiveContent.default,
val expandContentWarning: Boolean = AppearanceKeys.ExpandContentWarning.default,
val expandMediaSize: Boolean = AppearanceKeys.ExpandMediaSize.default,
+ val limitMediaGridToNine: Boolean = AppearanceKeys.LimitMediaGridToNine.default,
val videoAutoplay: VideoAutoplay = AppearanceKeys.VideoAutoplay.default,
val showLinkPreview: Boolean = AppearanceKeys.ShowLinkPreview.default,
val compatLinkPreview: Boolean = AppearanceKeys.CompatLinkPreview.default,
val showNumbers: Boolean = AppearanceKeys.ShowNumbers.default,
val postActionStyle: PostActionStyle = AppearanceKeys.PostActionStyle.default,
val postActionLayout: PostActionLayoutConfig = AppearanceKeys.PostActionLayout.default,
+ val postActionFixedWidth: Boolean = AppearanceKeys.PostActionFixedWidth.default,
val fullWidthPost: Boolean = AppearanceKeys.FullWidthPost.default,
val absoluteTimestamp: Boolean = AppearanceKeys.AbsoluteTimestamp.default,
val showPlatformLogo: Boolean = AppearanceKeys.ShowPlatformLogo.default,
@@ -87,12 +89,14 @@ public fun AppearancePatch.toTimelineAppearance(override: AppearancePatch?): Tim
showSensitiveContent = getTimelineValue(AppearanceKeys.ShowSensitiveContent, override),
expandContentWarning = getTimelineValue(AppearanceKeys.ExpandContentWarning, override),
expandMediaSize = getTimelineValue(AppearanceKeys.ExpandMediaSize, override),
+ limitMediaGridToNine = getTimelineValue(AppearanceKeys.LimitMediaGridToNine, override),
videoAutoplay = getTimelineValue(AppearanceKeys.VideoAutoplay, override),
showLinkPreview = getTimelineValue(AppearanceKeys.ShowLinkPreview, override),
compatLinkPreview = getTimelineValue(AppearanceKeys.CompatLinkPreview, override),
showNumbers = getTimelineValue(AppearanceKeys.ShowNumbers, override),
postActionStyle = getTimelineValue(AppearanceKeys.PostActionStyle, override),
postActionLayout = getTimelineValue(AppearanceKeys.PostActionLayout, override),
+ postActionFixedWidth = getTimelineValue(AppearanceKeys.PostActionFixedWidth, override),
fullWidthPost = getTimelineValue(AppearanceKeys.FullWidthPost, override),
absoluteTimestamp = getTimelineValue(AppearanceKeys.AbsoluteTimestamp, override),
showPlatformLogo = getTimelineValue(AppearanceKeys.ShowPlatformLogo, override),
@@ -107,12 +111,14 @@ public fun TimelineAppearance.withPatch(patch: AppearancePatch?): TimelineAppear
showSensitiveContent = patch.getTimelineValue(AppearanceKeys.ShowSensitiveContent, showSensitiveContent),
expandContentWarning = patch.getTimelineValue(AppearanceKeys.ExpandContentWarning, expandContentWarning),
expandMediaSize = patch.getTimelineValue(AppearanceKeys.ExpandMediaSize, expandMediaSize),
+ limitMediaGridToNine = patch.getTimelineValue(AppearanceKeys.LimitMediaGridToNine, limitMediaGridToNine),
videoAutoplay = patch.getTimelineValue(AppearanceKeys.VideoAutoplay, videoAutoplay),
showLinkPreview = patch.getTimelineValue(AppearanceKeys.ShowLinkPreview, showLinkPreview),
compatLinkPreview = patch.getTimelineValue(AppearanceKeys.CompatLinkPreview, compatLinkPreview),
showNumbers = patch.getTimelineValue(AppearanceKeys.ShowNumbers, showNumbers),
postActionStyle = patch.getTimelineValue(AppearanceKeys.PostActionStyle, postActionStyle),
postActionLayout = patch.getTimelineValue(AppearanceKeys.PostActionLayout, postActionLayout),
+ postActionFixedWidth = patch.getTimelineValue(AppearanceKeys.PostActionFixedWidth, postActionFixedWidth),
fullWidthPost = patch.getTimelineValue(AppearanceKeys.FullWidthPost, fullWidthPost),
absoluteTimestamp = patch.getTimelineValue(AppearanceKeys.AbsoluteTimestamp, absoluteTimestamp),
showPlatformLogo = patch.getTimelineValue(AppearanceKeys.ShowPlatformLogo, showPlatformLogo),
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt
index b72d52fd88..82521c88eb 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt
@@ -84,6 +84,8 @@ public class SettingsPresenter : PresenterBase() {
override fun updateExpandMediaSize(value: Boolean) = update(AppearanceKeys.ExpandMediaSize, value)
+ override fun updateLimitMediaGridToNine(value: Boolean) = update(AppearanceKeys.LimitMediaGridToNine, value)
+
override fun updateShowSensitiveContent(value: Boolean) = update(AppearanceKeys.ShowSensitiveContent, value)
override fun updateExpandContentWarning(value: Boolean) = update(AppearanceKeys.ExpandContentWarning, value)
@@ -102,6 +104,8 @@ public class SettingsPresenter : PresenterBase() {
override fun updatePostActionLayout(value: PostActionLayoutConfig) = update(AppearanceKeys.PostActionLayout, value)
+ override fun updatePostActionFixedWidth(value: Boolean) = update(AppearanceKeys.PostActionFixedWidth, value)
+
override fun updateShowNumbers(value: Boolean) = update(AppearanceKeys.ShowNumbers, value)
override fun updateAppSettings(block: AppSettings.() -> AppSettings) {
@@ -150,6 +154,8 @@ public class SettingsPresenter : PresenterBase() {
public fun updateExpandMediaSize(value: Boolean)
+ public fun updateLimitMediaGridToNine(value: Boolean)
+
public fun updateShowSensitiveContent(value: Boolean)
public fun updateExpandContentWarning(value: Boolean)
@@ -168,6 +174,8 @@ public class SettingsPresenter : PresenterBase() {
public fun updatePostActionLayout(value: PostActionLayoutConfig)
+ public fun updatePostActionFixedWidth(value: Boolean)
+
public fun updateShowNumbers(value: Boolean)
@WebIgnore
diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt b/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt
index 7829133c41..46e09f5eb9 100644
--- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt
+++ b/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt
@@ -28,6 +28,7 @@ class AppearancePatchTest {
fun emptyPatchSynthesizesDefaultAppearanceSettings() {
assertEquals(AppearanceSettings.Default, AppearancePatch.EMPTY.toAppearanceSettings())
assertFalse(TimelineAppearance.Default.expandContentWarning)
+ assertEquals(true, TimelineAppearance.Default.limitMediaGridToNine)
}
@Test
@@ -54,6 +55,8 @@ class AppearancePatchTest {
.set(AppearanceKeys.AvatarShape, AvatarShape.SQUARE)
.set(AppearanceKeys.ShowMedia, false)
.set(AppearanceKeys.ExpandContentWarning, true)
+ .set(AppearanceKeys.LimitMediaGridToNine, false)
+ .set(AppearanceKeys.PostActionFixedWidth, false)
.set(AppearanceKeys.TimelineDisplayMode, TimelineDisplayMode.Gallery)
assertEquals(
@@ -69,6 +72,8 @@ class AppearancePatchTest {
avatarShape = AvatarShape.SQUARE,
showMedia = false,
expandContentWarning = true,
+ limitMediaGridToNine = false,
+ postActionFixedWidth = false,
timelineDisplayMode = TimelineDisplayMode.Gallery,
),
patch.toTimelineAppearance(),
@@ -81,6 +86,7 @@ class AppearancePatchTest {
AppearancePatch.EMPTY
.set(AppearanceKeys.ShowMedia, false)
.set(AppearanceKeys.ShowNumbers, false)
+ .set(AppearanceKeys.PostActionFixedWidth, false)
.set(AppearanceKeys.ExpandContentWarning, false)
val timelinePatch =
AppearancePatch.EMPTY
@@ -92,6 +98,7 @@ class AppearancePatchTest {
showMedia = true,
expandContentWarning = true,
showNumbers = false,
+ postActionFixedWidth = false,
),
globalPatch.toTimelineAppearance(timelinePatch),
)
@@ -111,13 +118,17 @@ class AppearancePatchTest {
AppearancePatch.EMPTY
.set(AppearanceKeys.ShowMedia, true)
.set(AppearanceKeys.ExpandContentWarning, true)
+ .set(AppearanceKeys.LimitMediaGridToNine, false)
+ .set(AppearanceKeys.PostActionFixedWidth, false)
.set(AppearanceKeys.TimelineDisplayMode, TimelineDisplayMode.Gallery)
assertEquals(
TimelineAppearance(
showMedia = true,
expandContentWarning = true,
+ limitMediaGridToNine = false,
showNumbers = false,
+ postActionFixedWidth = false,
timelineDisplayMode = TimelineDisplayMode.Gallery,
aiConfig = TimelineAppearance.AiConfig(translation = true, tldr = true),
lineLimit = 9,
@@ -171,7 +182,9 @@ class AppearancePatchTest {
AppearanceKeys.ShowBottomBarLabels,
AppearanceKeys.DeckMode,
AppearanceKeys.ExpandContentWarning,
+ AppearanceKeys.LimitMediaGridToNine,
AppearanceKeys.PostActionLayout,
+ AppearanceKeys.PostActionFixedWidth,
)
val activeFields =
AppearanceSettings
@@ -227,8 +240,10 @@ class AppearancePatchTest {
.set(AppearanceKeys.DeckMode, true)
.set(AppearanceKeys.ShowMedia, false)
.set(AppearanceKeys.ExpandContentWarning, true)
+ .set(AppearanceKeys.LimitMediaGridToNine, false)
.set(AppearanceKeys.VideoAutoplay, VideoAutoplay.ALWAYS)
.set(AppearanceKeys.PostActionStyle, PostActionStyle.Stretch)
+ .set(AppearanceKeys.PostActionFixedWidth, false)
.set(
AppearanceKeys.PostActionLayout,
PostActionLayoutConfig(
diff --git a/web/src/lib/components/UiTimeline/post/postUtils.ts b/web/src/lib/components/UiTimeline/post/postUtils.ts
index d8fde1f037..1e0a6a0145 100644
--- a/web/src/lib/components/UiTimeline/post/postUtils.ts
+++ b/web/src/lib/components/UiTimeline/post/postUtils.ts
@@ -21,6 +21,7 @@ export const defaultTimelineAppearance = {
expandMediaSize: true,
fullWidthPost: false,
lineLimit: 5,
+ postActionFixedWidth: true,
postActionStyle: 'LeftAligned',
showLinkPreview: true,
showMedia: true,
diff --git a/web/src/routes/tabs/+page.svelte b/web/src/routes/tabs/+page.svelte
index 3a9dfaec36..1619fb352a 100644
--- a/web/src/routes/tabs/+page.svelte
+++ b/web/src/routes/tabs/+page.svelte
@@ -76,6 +76,7 @@
compatLinkPreview: false,
showNumbers: true,
postActionStyle: 'LeftAligned',
+ postActionFixedWidth: true,
fullWidthPost: false,
absoluteTimestamp: false,
showPlatformLogo: true,