Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  EmojiReactionsModalView.swift   Sprache: JAVA

 

import Combine
import SwiftUI
import ThreemaFramework
import ThreemaMacros

struct EmojiReactionModal: View {
    @Environment(\.presentationMode) private var presentationMode
    @Environment(\.colorScheme) var colorScheme
    @Environment(\.dismiss) var dismiss

    @Namespace private var menuItemTransition
    @State private var selectedIndex = 0
    
    @StateObject var model: ReactionModel
    
    var body: some View {
        if selectedIndex <= model.reactionEntries.count - 1 {
            let reaction = model.reactionEntries[selectedIndex]
            VStack(spacing: 0) {
                Spacer(minLength: 8)
                reactionsRow
                userList(
                    reaction.userReactionEntries,
                    for: reaction,
                    canBeRemoved: reaction.canBeRemoved
                )
            }
            .apply { view in
                if #available(iOS 26.0, *) {
                    view
                        .cornerRadius(30)
                        .padding(.top, 5)
                }
                else {
                    view
                        .cornerRadius(20)
                        .padding(.top, 10)
                }
            }
            .background(Color(UIColor.systemBackground))
        }
        else {
            VStack(spacing: 0) {
                Text(verbatim: "Error loading reactions. Try again later.")
            }
            .onAppear {
                dismiss()
            }
        }
    }
    
    private var menuItems: some View {
        ForEach(model.reactionEntries) { reactionEntry in
            let index = model.reactionEntries.firstIndex(of: reactionEntry) ?? 0
            VStack {
                MenuBarItem(
                    namespace: menuItemTransition,
                    isSelected: reactionEntry.reactedByMe,
                    value: Double(model.prevCountPerReaction[reactionEntry.reaction] ?? 0), content: .init(
                        emoji: reactionEntry.displayValue,
                        count: model.countPerReaction[reactionEntry.reaction] ?? 0
                    )
                )
                .padding(.top, 6)
                .padding(.bottom, 4)
                
                ZStack {
                    if selectedIndex == index {
                        Rectangle()
                            .frame(height: 2)
                            .foregroundColor(Colors.chatReactionBubbleTextColor.color)
                            .matchedGeometryEffect(id: "menuItemTransition", in: menuItemTransition)
                    }
                
                    Rectangle()
                        .frame(height: 2)
                        .foregroundColor(.clear)
                }
            }
            .id(reactionEntry)
            .frame(idealWidth: UIScreen.main.bounds.width / 5.5)
            .onTapGesture {
                withAnimation {
                    selectedIndex = index
                }
            }
        }
    }
    
    private var reactionsRow: some View {
        ScrollViewReader { scrollView in
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 0) {
                    menuItems
                }
            }
            .onChange(of: selectedIndex) {
                Task { @MainActor in
                    UIImpactFeedbackGenerator(style: .light).impactOccurred()
                }
                withAnimation {
                    scrollView.scrollTo(selectedIndex, anchor: .center)
                }
            }
            .background {
                VStack {
                    Spacer()
                    Rectangle()
                        .frame(height: 2)
                        .foregroundColor(.gray.opacity(0.5))
                }
            }
        }
    }
    
    private func userList(
        _ userReactions: [UserReactionEntry],
        for reaction: ReactionEntry,
        canBeRemoved: Bool
    ) -> some View {
        let sorted = userReactions.sorted {
            $0.isMe && !$1.isMe
        }
        
        return List(sorted) { user in
            HStack {
                user.profileImage
                    .resizable()
                    .scaledToFill()
                    .frame(width: 40, height: 40)
                    .clipShape(Circle())
                    .accessibilityHidden(true)
                
                Text(user.name)
                    .font(.subheadline)
                
                Spacer()
            }
            .accessibilityElement(children: .combine)
            .overlay(alignment: .trailing) {
                if canBeRemoved,
                   user.isMe,
                   let emoji = EmojiVariant(rawValue: reaction.reaction) {
                    HStack {
                        Spacer()
                        Button(role: .destructive) {
                            UIImpactFeedbackGenerator(style: .light).impactOccurred()
                            model.removeOwnReaction(emoji)
                        }
                        label: {
                            Image(systemName: "trash")
                                .foregroundColor(.red)
                        }
                        .accessibilityLabel(#localize("delete"))
                    }
                }
            }
        }
        .listStyle(.plain)
    }
}

// MARK: - EmojiReactionModal.MenuBarItem

extension EmojiReactionModal {
    struct MenuBarItem: View {
        struct Content {
            let emoji: String
            let count: Int
        }
        
        let namespace: Namespace.ID
        var isSelected: Bool
        
        @State var value: Double = 0
        @State var content: Content
        
        var body: some View {
            VStack {
                HStack(spacing: 2) {
                    Text(content.emoji)
                        .font(.title3)
                    
                    Text(String(format: "%.0f", value))
                        .contentTransition(.numericText(value: value))
                        .font(.subheadline)
                        .fontWeight(isSelected ? .bold : .regular)
                        .foregroundColor(Colors.chatReactionBubbleTextColor.color)
                }
            }
            .dynamicTypeSize(...DynamicTypeSize.accessibility3)
            .accessibilityElement(children: .combine)
            .onAppear {
                updateValue(
                    oldCount: Int(value),
                    newCount: content.count
                )
            }
        }
        
        private func updateValue(oldCount: Int, newCount: Int) {
            if newCount > oldCount {
                let delta = newCount - oldCount
                withAnimation {
                    value += Double(delta)
                }
            }
            else if newCount < oldCount {
                let delta = oldCount - newCount
                withAnimation {
                    value -= Double(delta)
                }
            }
        }
    }
}

typealias EmojiReactionModalViewController = UIHostingController<EmojiReactionModal>
extension EmojiReactionModalViewController {
    static func sheet(_ reactionsManager: any ReactionsModalDelegate) -> EmojiReactionModalViewController {
        UIHostingController(
            rootView: EmojiReactionModal(model: .init(reactionsManager))
        ).then { vc in
            if let sheetController = vc.presentationController as? UISheetPresentationController {
                sheetController.detents = [.medium(), .large()]
                sheetController.prefersGrabberVisible = true
                sheetController.prefersScrollingExpandsWhenScrolledToEdge = true
            }
            let window = UIApplication.shared.windows.first(where: \.isKeyWindow)
            let inverseSafeAreaInset = window?.safeAreaInsets.bottom ?? 0
            vc.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: -inverseSafeAreaInset, right: 0)
        }
    }
}

Messung
CHZG
859810094

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104857
#Domains=439316