mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-11 11:04:56 +00:00
show hide search field by observing scroll position
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import SwiftUIIntrospect
|
||||
|
||||
private weak var collectionView: UICollectionView?
|
||||
|
||||
struct ChatListView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@@ -21,6 +24,9 @@ struct ChatListView: View {
|
||||
@State private var userPickerVisible = false
|
||||
@State private var showConnectDesktop = false
|
||||
|
||||
@State private var isSearchExpanded = true
|
||||
@State private var contentOffsetObservation: NSKeyValueObservation?
|
||||
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
|
||||
@@ -86,15 +92,47 @@ struct ChatListView: View {
|
||||
))
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
if oneHandUI { Divider().background(Material.ultraThin) }
|
||||
if oneHandUI {
|
||||
Divider().background(Material.thin)
|
||||
} else {
|
||||
searchBar
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
if oneHandUI { searchBar }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var searchBar: some View {
|
||||
// TODO: Preserve height, without hardcoding it. Remove `.font(.system(size: 18))` after done.
|
||||
let height: Double = 56
|
||||
let isVisible = isSearchExpanded || searchFocussed
|
||||
VStack(spacing: 0) {
|
||||
if oneHandUI { Divider() }
|
||||
ChatListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.padding(8)
|
||||
.frame(height: isVisible ? height : 0)
|
||||
.opacity(isVisible ? 1 : 0)
|
||||
if !oneHandUI { Divider() }
|
||||
}
|
||||
.background(Material.thin)
|
||||
.padding(oneHandUI ? .top : .bottom, isVisible ? 0 : height)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func withToolbar(content: () -> some View) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
content()
|
||||
.toolbarBackground(.visible, for: oneHandUI ? .bottomBar : .navigationBar)
|
||||
.toolbarBackground(.hidden, for: .navigationBar)
|
||||
.toolbarBackground(.hidden, for: .bottomBar)
|
||||
.toolbar {
|
||||
if oneHandUI {
|
||||
bottomToolbar
|
||||
@@ -152,10 +190,7 @@ struct ChatListView: View {
|
||||
@ViewBuilder
|
||||
var principalToolbarItem: some View {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
// TODO: For testing, remove after oneHandUI is implemented
|
||||
.contentShape(Rectangle()).onTapGesture { oneHandUI.toggle() }
|
||||
Text("Chats").font(.headline)
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -174,19 +209,6 @@ struct ChatListView: View {
|
||||
let cs = filteredChats()
|
||||
ZStack {
|
||||
List {
|
||||
if !chatModel.chats.isEmpty {
|
||||
ChatListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
if !oneHandUICardShown {
|
||||
OneHandUICard()
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
@@ -202,6 +224,7 @@ struct ChatListView: View {
|
||||
}
|
||||
.offset(x: -8)
|
||||
}
|
||||
.introspect(.list, on: .iOS(.v16, .v17, .v18)) { setObservations(for: $0) }
|
||||
.listStyle(.plain)
|
||||
.onChange(of: chatModel.chatId) { chId in
|
||||
if chId == nil, let chatId = chatModel.chatToTop {
|
||||
@@ -224,6 +247,33 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func setObservations(for cv: UICollectionView) {
|
||||
if collectionView != cv {
|
||||
collectionView = cv
|
||||
var scrollDistance: CGFloat = 0
|
||||
contentOffsetObservation?.invalidate()
|
||||
contentOffsetObservation = cv.observe(
|
||||
\.contentOffset,
|
||||
options: [.new, .old]
|
||||
) { (cv, change) in
|
||||
if let newOffset = change.newValue?.y,
|
||||
let oldOffset = change.oldValue?.y {
|
||||
let bottomOffset = cv.contentSize.height - cv.visibleSize.height - newOffset + cv.safeAreaInsets.bottom
|
||||
// Show/Hide search bar when scrolled for more than `MAX` amount
|
||||
if newOffset > .zero,
|
||||
bottomOffset > 0 {
|
||||
let MAX: CGFloat = 64
|
||||
scrollDistance = min(max(scrollDistance + oldOffset - newOffset, -MAX), +MAX)
|
||||
if (isSearchExpanded && scrollDistance == -MAX) ||
|
||||
(!isSearchExpanded && scrollDistance == +MAX) {
|
||||
withAnimation(.easeOut(duration: 0.15)) { isSearchExpanded.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View {
|
||||
Circle()
|
||||
.frame(width: size, height: size)
|
||||
@@ -366,39 +416,37 @@ struct ChatListSearchBar: View {
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
TextField("Search or paste SimpleX link", text: $searchText)
|
||||
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
|
||||
.disabled(searchShowingSimplexLink)
|
||||
.focused($searchFocussed)
|
||||
.frame(maxWidth: .infinity)
|
||||
if !searchText.isEmpty {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
TextField("Search or paste SimpleX link", text: $searchText)
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(searchShowingSimplexLink ? theme.colors.secondary : theme.colors.onBackground)
|
||||
.disabled(searchShowingSimplexLink)
|
||||
.focused($searchFocussed)
|
||||
.frame(maxWidth: .infinity)
|
||||
if !searchText.isEmpty {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
CE38A29A2C3FCA54005ED185 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; };
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; };
|
||||
CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; };
|
||||
CE7E3A472C628EAD00BECA8F /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = CE7E3A462C628EAD00BECA8F /* SwiftUIIntrospect */; };
|
||||
CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; };
|
||||
CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; };
|
||||
CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; };
|
||||
@@ -620,6 +621,7 @@
|
||||
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */,
|
||||
8C8118722C220B5B00E6FC94 /* Yams in Frameworks */,
|
||||
D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */,
|
||||
CE7E3A472C628EAD00BECA8F /* SwiftUIIntrospect in Frameworks */,
|
||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */,
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */,
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */,
|
||||
@@ -1149,6 +1151,7 @@
|
||||
D7F0E33829964E7E0068AF69 /* LZString */,
|
||||
D7197A1729AE89660055C05A /* WebRTC */,
|
||||
8C8118712C220B5B00E6FC94 /* Yams */,
|
||||
CE7E3A462C628EAD00BECA8F /* SwiftUIIntrospect */,
|
||||
);
|
||||
productName = "SimpleX (iOS)";
|
||||
productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */;
|
||||
@@ -1293,6 +1296,7 @@
|
||||
D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */,
|
||||
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */,
|
||||
8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */,
|
||||
CE7E3A452C628EAD00BECA8F /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
|
||||
);
|
||||
productRefGroup = 5CA059CB279559F40002BEB4 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -2334,6 +2338,14 @@
|
||||
version = 5.1.2;
|
||||
};
|
||||
};
|
||||
CE7E3A452C628EAD00BECA8F /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/siteline/swiftui-introspect.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.3.0;
|
||||
};
|
||||
};
|
||||
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/simplex-chat/WebRTC.git";
|
||||
@@ -2376,6 +2388,11 @@
|
||||
package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */;
|
||||
productName = SwiftyGif;
|
||||
};
|
||||
CE7E3A462C628EAD00BECA8F /* SwiftUIIntrospect */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CE7E3A452C628EAD00BECA8F /* XCRemoteSwiftPackageReference "swiftui-introspect" */;
|
||||
productName = SwiftUIIntrospect;
|
||||
};
|
||||
D7197A1729AE89660055C05A /* WebRTC */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */;
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115",
|
||||
"originHash" : "415d895b8472ba92f7fa95973495513f544dcde3cbf09f6928a489d8c8731c14",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "codescanner",
|
||||
@@ -18,6 +18,15 @@
|
||||
"revision" : "7f62f21de5b18582a950e1753b775cc614722407"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-introspect",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/siteline/swiftui-introspect.git",
|
||||
"state" : {
|
||||
"revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
Reference in New Issue
Block a user