Add instance information.

This commit is contained in:
Marcin Czachursk 2023-03-26 09:40:05 +02:00
parent f4322f74e4
commit 8ec3817c52
15 changed files with 209 additions and 19 deletions

View File

@ -95,6 +95,7 @@
"userProfile.title.unfollow" = "Unfollow";
"userProfile.title.followBack" = "Follow back";
"userProfile.title.follow" = "Follow";
"userProfile.title.instance" = "Instance information";
"userProfile.error.notExists" = "Account does not exists.";
"userProfile.error.loadingAccountFailed" = "Error during download account from server.";
"userProfile.error.muting" = "Muting/unmuting action failed.";
@ -273,3 +274,21 @@
"editProfile.error.loadingAvatarFailed" = "Loading avatar failed.";
"editProfile.error.noProfileData" = "Profile data cannot be displayed.";
"editProfile.error.loadingAccountFailed" = "Error during download account from server.";
// Mark: Instance information.
"instance.navigationBar.title" = "Instance";
"instance.title.instanceInfo" = "Instance info";
"instance.title.name" = "Name";
"instance.title.address" = "Address";
"instance.title.email" = "Email";
"instance.title.version" = "Version";
"instance.title.users" = "Users";
"instance.title.posts" = "Posts";
"instance.title.domains" = "Domains";
"instance.title.registrations" = "Registrations";
"instance.title.approvalRequired" = "Approval required";
"instance.title.rules" = "Instance rules";
"instance.title.contact" = "Contact";
"instance.title.pixelfedAccount" = "Pixelfed account";
"instance.error.noInstanceData" = "Instance data cannot be displayed.";
"instance.error.loadingDataFailed" = "Error during download instance data from server.";

View File

@ -95,6 +95,7 @@
"userProfile.title.unfollow" = "Przestań obserwować";
"userProfile.title.followBack" = "Również obserwuj";
"userProfile.title.follow" = "Obserwuj";
"userProfile.title.instance" = "Informacje o instancji";
"userProfile.error.notExists" = "Konto nie istnieje.";
"userProfile.error.notExists" = "Błąd podczas pobierania danych użytkownika.";
"userProfile.error.mute" = "Błąd podczas wyciszania użytkownika.";
@ -273,3 +274,21 @@
"editProfile.error.loadingAvatarFailed" = "Błąd podczas wczytywania zdjęcia.";
"editProfile.error.noProfileData" = "Dane profilu nie mogą zostać wyświetlone.";
"editProfile.error.loadingAccountFailed" = "Błąd podczas pobierania profilu użytkownika.";
// Mark: Instance information.
"instance.navigationBar.title" = "Instancja";
"instance.title.instanceInfo" = "Informacja o instancji";
"instance.title.name" = "Nazwa";
"instance.title.address" = "Adres";
"instance.title.email" = "Email";
"instance.title.version" = "Wersja";
"instance.title.users" = "Użytkownicy";
"instance.title.posts" = "Postów";
"instance.title.domains" = "Domen";
"instance.title.registrations" = "Rejestracja";
"instance.title.approvalRequired" = "Akeptowanie rejestracji";
"instance.title.rules" = "Reguły instancji";
"instance.title.contact" = "Kontakt";
"instance.title.pixelfedAccount" = "Konto Pixelfed";
"instance.error.noInstanceData" = "Dane instancji nie mogą zostać wyświetlone.";
"instance.error.loadingDataFailed" = "Błąd podczas pobierania danych instancji.";

View File

@ -50,6 +50,9 @@ public struct Instance: Codable {
/// Statistics about the instance.
public let stats: Stats?
/// Contact account.
public let contactAccount: Account?
enum CodingKeys: String, CodingKey {
case uri
case title
@ -65,6 +68,7 @@ public struct Instance: Codable {
case registrations
case approvalRequired = "approval_required"
case stats
case contactAccount = "contact_account"
}
public init(from decoder: Decoder) throws {
@ -84,5 +88,6 @@ public struct Instance: Codable {
self.registrations = (try? container.decodeIfPresent(Bool.self, forKey: .registrations)) ?? false
self.approvalRequired = (try? container.decodeIfPresent(Bool.self, forKey: .approvalRequired)) ?? false
self.stats = try? container.decodeIfPresent(Stats.self, forKey: .stats)
self.contactAccount = try? container.decodeIfPresent(Account.self, forKey: .contactAccount)
}
}

View File

@ -163,6 +163,7 @@
F89AC00729A208CC00F4159F /* PlaceSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89AC00629A208CC00F4159F /* PlaceSelectorView.swift */; };
F89AC00929A20C5C00F4159F /* Client+Places.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89AC00829A20C5C00F4159F /* Client+Places.swift */; };
F89B5CC029D019B600549F2F /* HTMLString in Frameworks */ = {isa = PBXBuildFile; productRef = F89B5CBF29D019B600549F2F /* HTMLString */; };
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89B5CC129D01BF700549F2F /* InstanceView.swift */; };
F89CEB802984198600A1376F /* AttachmentData+HighestImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */; };
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C3E29716E41001DA3D4 /* Theme.swift */; };
F89D6C4229717FDC001DA3D4 /* AccountsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4129717FDC001DA3D4 /* AccountsSectionView.swift */; };
@ -374,6 +375,7 @@
F89AC00429A1F9B500F4159F /* AppMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMetadata.swift; sourceTree = "<group>"; };
F89AC00629A208CC00F4159F /* PlaceSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceSelectorView.swift; sourceTree = "<group>"; };
F89AC00829A20C5C00F4159F /* Client+Places.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Client+Places.swift"; sourceTree = "<group>"; };
F89B5CC129D01BF700549F2F /* InstanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceView.swift; sourceTree = "<group>"; };
F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+HighestImage.swift"; sourceTree = "<group>"; };
F89D6C3E29716E41001DA3D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
F89D6C4129717FDC001DA3D4 /* AccountsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSectionView.swift; sourceTree = "<group>"; };
@ -521,6 +523,7 @@
F8FA991D299FAB92007AB130 /* PhotoEditorView.swift */,
F89AC00629A208CC00F4159F /* PlaceSelectorView.swift */,
F8E6D03229CDD52500416CCA /* EditProfileView.swift */,
F89B5CC129D01BF700549F2F /* InstanceView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -1216,6 +1219,7 @@
F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */,
F802884F297AEED5000BDD51 /* DatabaseError.swift in Sources */,
F86A4307299AA5E900DF7645 /* ThanksView.swift in Sources */,
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */,
F88AB05829B36B8200345EDE /* AccountsPhotoView.swift in Sources */,
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
F8B9B356298D4C1E009CC69C /* Client+Instance.swift in Sources */,
@ -1282,7 +1286,7 @@
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 84;
CURRENT_PROJECT_VERSION = 85;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1310,7 +1314,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 84;
CURRENT_PROJECT_VERSION = 85;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1458,7 +1462,7 @@
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 84;
CURRENT_PROJECT_VERSION = 85;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES;
@ -1498,7 +1502,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 84;
CURRENT_PROJECT_VERSION = 85;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES;

View File

@ -46,6 +46,8 @@ extension View {
SearchView()
case .editProfile:
EditProfileView()
case .instance:
InstanceView()
}
}
}

View File

@ -23,6 +23,7 @@ enum RouteurDestinations: Hashable {
case accountsPhoto(listType: AccountsPhotoView.ListType)
case search
case editProfile
case instance
}
enum SheetDestinations: Identifiable {

View File

@ -61,7 +61,6 @@ struct ComposeView: View {
}
private let statusViewModel: StatusModel?
private let contentWidth = Int(UIScreen.main.bounds.width) - 50
private let keyboardFontImageSize = 20.0
private let keyboardFontTextSize = 16.0
private let autocompleteFontTextSize = 12.0
@ -216,7 +215,8 @@ struct ComposeView: View {
Spacer()
}
MarkdownFormattedText(status.content.asMarkdown, withFontSize: 14, andWidth: contentWidth)
MarkdownFormattedText(status.content.asMarkdown)
.font(.subheadline)
.environment(\.openURL, OpenURLAction { url in .handled })
}
}

View File

@ -0,0 +1,135 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
import Foundation
import PixelfedKit
struct InstanceView: View {
@EnvironmentObject private var applicationState: ApplicationState
@EnvironmentObject private var routerPath: RouterPath
@EnvironmentObject private var client: Client
@State private var state: ViewState = .loading
@State private var instance: Instance?
var body: some View {
switch state {
case .loading:
LoadingIndicator()
.task {
await self.loadData()
}
case .loaded:
if let instance = self.instance {
self.details(instance: instance)
} else {
NoDataView(imageSystemName: "server.rack", text: "instance.error.noInstanceData")
}
case .error(let error):
ErrorView(error: error) {
self.state = .loading
await self.loadData()
}
.padding()
}
}
@ViewBuilder
private func details(instance: Instance) -> some View {
List {
Section("instance.title.instanceInfo") {
self.dataRow(title: "instance.title.name", value: instance.title ?? String.empty())
self.dataRow(title: "instance.title.address", value: "https://\(instance.uri)")
VStack(alignment: .leading) {
if let description = instance.description {
MarkdownFormattedText(description.asMarkdown)
.font(.subheadline)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url)
})
.padding(.vertical, 4)
}
if let shortDescription = instance.shortDescription {
Text(shortDescription)
.font(.footnote)
.foregroundColor(.lightGrayColor)
}
}
self.dataRow(title: "instance.title.version", value: instance.version)
self.dataRow(title: "instance.title.users", value: "\(instance.stats?.userCount ?? 0)")
self.dataRow(title: "instance.title.posts", value: "\(instance.stats?.statusCount ?? 0)")
self.dataRow(title: "instance.title.domains", value: "\(instance.stats?.domainCount ?? 0)")
Toggle("instance.title.registrations", isOn: Binding.constant(instance.registrations))
.disabled(true)
Toggle("instance.title.approvalRequired", isOn: Binding.constant(instance.approvalRequired))
.disabled(true)
}
Section("instance.title.contact") {
self.dataRow(title: "instance.title.email", value: instance.email ?? String.empty())
if let contactAccount = instance.contactAccount {
NavigationLink(value: RouteurDestinations.userProfile(
accountId: contactAccount.id,
accountDisplayName: contactAccount.displayNameWithoutEmojis,
accountUserName: contactAccount.acct)
) {
HStack {
Text("instance.title.pixelfedAccount", comment: "Pixelfed account")
Spacer()
Text("@\(contactAccount.displayNameWithoutEmojis)")
.font(.subheadline)
.foregroundColor(.accentColor)
}
}
}
}
if let rules = self.instance?.rules {
Section("instance.title.rules") {
ForEach(rules, id: \.id) { rule in
Text(rule.text)
}
}
}
}
.navigationTitle("instance.navigationBar.title")
}
@ViewBuilder
private func dataRow(title: LocalizedStringKey, value: String) -> some View {
HStack {
Text(title, comment: "Title")
Spacer()
Text(value)
.foregroundColor(.lightGrayColor)
.font(.subheadline)
}
}
private func loadData() async {
do {
if let serverUrl = self.applicationState.account?.serverUrl {
self.instance = try await self.client.instances.instance(url: serverUrl)
}
self.state = .loaded
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "instance.error.loadingDataFailed", showToastr: true)
self.state = .error(error)
} else {
ErrorService.shared.handle(error, message: "instance.error.loadingDataFailed", showToastr: false)
}
}
}
}

View File

@ -17,7 +17,6 @@ struct NotificationRowView: View {
private var attachment: MediaAttachment?
private var notification: PixelfedKit.Notification
private let contentWidth = Int(UIScreen.main.bounds.width) - 150
public init(notification: PixelfedKit.Notification) {
self.notification = notification
@ -91,14 +90,16 @@ struct NotificationRowView: View {
}
case .mention:
if let status = self.notification.status {
MarkdownFormattedText(status.content.asMarkdown, withFontSize: 12, andWidth: contentWidth)
MarkdownFormattedText(status.content.asMarkdown)
.font(.caption)
.environment(\.openURL, OpenURLAction { url in .handled })
} else {
EmptyView()
}
case .follow, .followRequest, .adminSignUp:
if let note = self.notification.account.note {
MarkdownFormattedText(note.asMarkdown, withFontSize: 12, andWidth: contentWidth)
MarkdownFormattedText(note.asMarkdown)
.font(.caption)
.environment(\.openURL, OpenURLAction { url in .handled })
} else {
EmptyView()

View File

@ -60,7 +60,8 @@ struct InstanceRowView: View {
}
if let description = instance.description {
MarkdownFormattedText(description.asMarkdown, withFontSize: 14)
MarkdownFormattedText(description.asMarkdown)
.font(.subheadline)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url)
})

View File

@ -80,6 +80,7 @@ struct StatusView: View {
}
MarkdownFormattedText(statusViewModel.content.asMarkdown)
.font(.callout)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url)
})

View File

@ -12,7 +12,6 @@ struct CommentBodyView: View {
@EnvironmentObject var routerPath: RouterPath
@State var statusViewModel: StatusModel
private let contentWidth = Int(UIScreen.main.bounds.width) - 60
var body: some View {
HStack (alignment: .top) {
@ -37,7 +36,8 @@ struct CommentBodyView: View {
.font(.footnote)
}
MarkdownFormattedText(self.statusViewModel.content.asMarkdown, withFontSize: 14, andWidth: contentWidth)
MarkdownFormattedText(self.statusViewModel.content.asMarkdown)
.font(.footnote)
.environment(\.openURL, OpenURLAction { url in .handled })
.padding(.top, 4)

View File

@ -74,7 +74,8 @@ struct UserProfileHeaderView: View {
}
if let note = account.note, !note.asMarkdown.isEmpty {
MarkdownFormattedText(note.asMarkdown, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
MarkdownFormattedText(note.asMarkdown)
.font(.subheadline)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url)
})

View File

@ -160,6 +160,12 @@ struct UserProfileView: View {
Divider()
}
NavigationLink(value: RouteurDestinations.instance) {
Label(NSLocalizedString("userProfile.title.instance", comment: "Instance information"), systemImage: "server.rack")
}
Divider()
NavigationLink(value: RouteurDestinations.favourites) {
Label(NSLocalizedString("userProfile.title.favourites", comment: "Favourites"), systemImage: "hand.thumbsup")

View File

@ -14,17 +14,12 @@ struct MarkdownFormattedText: View {
private let markdown: String
private let textView = UITextView()
private let fontSize: CGFloat
private let width: Int
init(_ markdown: String, withFontSize fontSize: CGFloat = 16, andWidth width: Int? = nil) {
init(_ markdown: String) {
self.markdown = markdown
self.fontSize = fontSize
self.width = width ?? Int(UIScreen.main.bounds.width) - 16
}
var body: some View {
EmojiText(markdown: markdown, emojis: [])
.font(.system(size: self.fontSize))
}
}