Eliminate popping when scrolling
This commit makes a few changes: - Link preview views are no longer cached, only the metadata. This fixes a memory leak when preview videos. It will keep playing the video forever eventually leading to a crash. This is fixed! - Cache the intrinsic height of previews, when loading notes it looks for the cached height so that things don't pop-in after the fact - Note artifacts and previews are set in the constructor instead of onAppear, this prevents the size from changing and popping after it has been loaded into the lazyvstack Changelog-Fixed: Fix memory leak with inline videos Changelog-Fixed: Eliminate popping when scrolling
This commit is contained in:
@@ -1649,7 +1649,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1691,7 +1691,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import LinkPresentation
|
|||||||
|
|
||||||
class CustomLinkView: LPLinkView {
|
class CustomLinkView: LPLinkView {
|
||||||
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
|
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Metadata {
|
enum Metadata {
|
||||||
case linkmeta(LPLinkMetadata)
|
case linkmeta(CachedMetadata)
|
||||||
case url(URL)
|
case url(URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +27,19 @@ struct LinkViewRepresentable: UIViewRepresentable {
|
|||||||
func makeUIView(context: Context) -> CustomLinkView {
|
func makeUIView(context: Context) -> CustomLinkView {
|
||||||
switch meta {
|
switch meta {
|
||||||
case .linkmeta(let linkmeta):
|
case .linkmeta(let linkmeta):
|
||||||
return CustomLinkView(metadata: linkmeta)
|
return CustomLinkView(metadata: linkmeta.meta)
|
||||||
case .url(let url):
|
case .url(let url):
|
||||||
return CustomLinkView(url: url)
|
return CustomLinkView(url: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: CustomLinkView, context: Context) {
|
func updateUIView(_ uiView: CustomLinkView, context: Context) {
|
||||||
|
switch meta {
|
||||||
|
case .linkmeta(let cached):
|
||||||
|
cached.intrinsic_height = uiView.intrinsicContentSize.height
|
||||||
|
case .url:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LinkPresentation
|
import LinkPresentation
|
||||||
|
|
||||||
|
class CachedMetadata {
|
||||||
|
let meta: LPLinkMetadata
|
||||||
|
var intrinsic_height: CGFloat?
|
||||||
|
|
||||||
|
init(meta: LPLinkMetadata) {
|
||||||
|
self.meta = meta
|
||||||
|
self.intrinsic_height = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Preview {
|
enum Preview {
|
||||||
case value(LinkViewRepresentable)
|
case value(CachedMetadata)
|
||||||
case failed
|
case failed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,12 +30,12 @@ class PreviewCache {
|
|||||||
return previews[evid]
|
return previews[evid]
|
||||||
}
|
}
|
||||||
|
|
||||||
func store(evid: String, preview: LinkViewRepresentable?) {
|
func store(evid: String, preview: LPLinkMetadata?) {
|
||||||
switch preview {
|
switch preview {
|
||||||
case .none:
|
case .none:
|
||||||
previews[evid] = .failed
|
previews[evid] = .failed
|
||||||
case .some(let meta):
|
case .some(let meta):
|
||||||
previews[evid] = .value(meta)
|
previews[evid] = .value(CachedMetadata(meta: meta))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,21 @@ struct NoteContentView: View {
|
|||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let show_images: Bool
|
let show_images: Bool
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
|
let preview_height: CGFloat?
|
||||||
|
|
||||||
@State var artifacts: NoteArtifacts
|
@State var artifacts: NoteArtifacts
|
||||||
@State var preview: LinkViewRepresentable? = nil
|
@State var preview: LinkViewRepresentable?
|
||||||
|
|
||||||
|
init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, artifacts: NoteArtifacts) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.event = event
|
||||||
|
self.show_images = show_images
|
||||||
|
self.size = size
|
||||||
|
self._artifacts = State(initialValue: artifacts)
|
||||||
|
self.preview_height = lookup_cached_preview_size(previews: damus_state.previews, evid: event.id)
|
||||||
|
self._preview = State(initialValue: load_cached_preview(previews: damus_state.previews, evid: event.id))
|
||||||
|
self._artifacts = State(initialValue: render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey))
|
||||||
|
}
|
||||||
|
|
||||||
func MainContent() -> some View {
|
func MainContent() -> some View {
|
||||||
return VStack(alignment: .leading) {
|
return VStack(alignment: .leading) {
|
||||||
@@ -58,23 +70,21 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let preview = self.preview, show_images {
|
if let preview = self.preview, show_images {
|
||||||
preview
|
if let preview_height {
|
||||||
} else {
|
preview
|
||||||
ForEach(artifacts.links, id:\.self) { link in
|
.frame(height: preview_height)
|
||||||
if let url = link {
|
} else {
|
||||||
LinkViewRepresentable(meta: .url(url))
|
preview
|
||||||
.frame(height: 50)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if let link = artifacts.links.first {
|
||||||
|
LinkViewRepresentable(meta: .url(link))
|
||||||
|
.frame(height: 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
MainContent()
|
MainContent()
|
||||||
.onAppear() {
|
|
||||||
self.artifacts = render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
|
||||||
}
|
|
||||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||||
let profile = notif.object as! ProfileUpdate
|
let profile = notif.object as! ProfileUpdate
|
||||||
let blocks = event.blocks(damus_state.keypair.privkey)
|
let blocks = event.blocks(damus_state.keypair.privkey)
|
||||||
@@ -92,21 +102,19 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
if let preview = damus_state.previews.lookup(self.event.id) {
|
guard self.preview == nil else {
|
||||||
switch preview {
|
return
|
||||||
case .value(let view):
|
|
||||||
self.preview = view
|
|
||||||
case .failed:
|
|
||||||
// don't try to refetch meta if we've failed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_images, artifacts.links.count == 1 {
|
if show_images, artifacts.links.count == 1 {
|
||||||
let meta = await getMetaData(for: artifacts.links.first!)
|
let meta = await getMetaData(for: artifacts.links.first!)
|
||||||
|
|
||||||
let view = meta.map { LinkViewRepresentable(meta: .linkmeta($0)) }
|
damus_state.previews.store(evid: self.event.id, preview: meta)
|
||||||
damus_state.previews.store(evid: self.event.id, preview: view)
|
guard case .value(let cached) = damus_state.previews.lookup(self.event.id) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let view = LinkViewRepresentable(meta: .linkmeta(cached))
|
||||||
|
|
||||||
self.preview = view
|
self.preview = view
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,3 +241,23 @@ func is_image_url(_ url: URL) -> Bool {
|
|||||||
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
|
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookup_cached_preview_size(previews: PreviewCache, evid: String) -> CGFloat? {
|
||||||
|
guard case .value(let cached) = previews.lookup(evid) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let height = cached.intrinsic_height else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func load_cached_preview(previews: PreviewCache, evid: String) -> LinkViewRepresentable? {
|
||||||
|
guard case .value(let meta) = previews.lookup(evid) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return LinkViewRepresentable(meta: .linkmeta(meta))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user