├── .gitattributes
├── .github
└── workflows
│ ├── delete_old_workflow_runs.yml
│ └── preflight.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── BuildTools
├── Empty.swift
├── Package.resolved
├── Package.swift
├── swiftgen.yml
└── xcassets-colors-swiftui.stencil
├── CHANGELOG.md
├── LICENSE.md
├── Makefile
├── Prose
├── .gitignore
├── ConversationFeaturePreview
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ContentView.swift
│ ├── ConversationFeaturePreview.entitlements
│ ├── ConversationFeaturePreviewApp.swift
│ └── Preview Content
│ │ └── Preview Assets.xcassets
│ │ └── Contents.json
├── EditProfileFeaturePreview
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── EditProfileFeaturePreview.entitlements
│ └── EditProfileFeaturePreviewApp.swift
├── Prose.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── ConversationFeaturePreview.xcscheme
│ │ ├── Prose.xcscheme
│ │ ├── Release.xcscheme
│ │ └── UITestHost.xcscheme
├── Prose
│ ├── AllTests.xctestplan
│ ├── AppDelegate.swift
│ ├── Components
│ │ └── Sidebar
│ │ │ └── SidebarPartContextComponent.swift
│ ├── Package.resolved
│ ├── Prose.entitlements
│ ├── ProseApp.swift
│ ├── Resources
│ │ ├── Assets.xcassets
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Dock@1024.png
│ │ │ │ ├── Dock@128.png
│ │ │ │ ├── Dock@16.png
│ │ │ │ ├── Dock@256 1.png
│ │ │ │ ├── Dock@256.png
│ │ │ │ ├── Dock@32 1.png
│ │ │ │ ├── Dock@32.png
│ │ │ │ ├── Dock@512 1.png
│ │ │ │ ├── Dock@512.png
│ │ │ │ └── Dock@64.png
│ │ │ └── Contents.json
│ │ └── Credits.rtf
│ ├── UITests.xctestplan
│ └── UnitTests.xctestplan
├── ProseLib
│ ├── .gitignore
│ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── Package.resolved
│ ├── Package.swift
│ ├── Sources
│ │ ├── AddressBookFeature
│ │ │ ├── AddressBookScreen.swift
│ │ │ ├── Target+Logger.swift
│ │ │ └── Toolbar.swift
│ │ ├── App
│ │ │ ├── Accounts
│ │ │ │ ├── AccountReducer.swift
│ │ │ │ ├── AccountsReducer.swift
│ │ │ │ └── SelectedAccountReducer.swift
│ │ │ ├── AppDelegate.swift
│ │ │ ├── AppReducer.swift
│ │ │ ├── AppScene.swift
│ │ │ └── Target+Logger.swift
│ │ ├── AppDomain
│ │ │ ├── Account.swift
│ │ │ ├── ConnectionStatus.swift
│ │ │ ├── Connectivity.swift
│ │ │ ├── Credentials.swift
│ │ │ ├── Exports.swift
│ │ │ ├── ProseCoreExtensions
│ │ │ │ ├── Availability.swift
│ │ │ │ ├── BareJid.swift
│ │ │ │ ├── Contact.swift
│ │ │ │ ├── FullJid.swift
│ │ │ │ ├── Message.swift
│ │ │ │ └── UserProfile.swift
│ │ │ ├── SessionState.swift
│ │ │ └── UserInfo.swift
│ │ ├── AppLocalization
│ │ │ ├── AppDomain+i18n.swift
│ │ │ ├── Bundle+Fix.swift
│ │ │ ├── Generated
│ │ │ │ ├── .gitkeep
│ │ │ │ └── Strings+Generated.swift
│ │ │ ├── Resources
│ │ │ │ └── en.lproj
│ │ │ │ │ ├── Localizable.strings
│ │ │ │ │ └── Localizable.stringsdict
│ │ │ ├── String+Markdown.swift
│ │ │ └── Target+Logger.swift
│ │ ├── Assets
│ │ │ ├── Bundle+Fix.swift
│ │ │ ├── Generated
│ │ │ │ ├── .gitkeep
│ │ │ │ ├── Colors+Generated.swift
│ │ │ │ └── Images+Generated.swift
│ │ │ ├── ImageAsset+URL.swift
│ │ │ ├── Resources
│ │ │ │ ├── Colors.xcassets
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── background
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── message.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── border
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── primary.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── secondary.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── tertiary.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── tertiaryLight.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── state
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── coolGrey.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── green.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── greenLight.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── grey.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── greyLight.colorset
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── text
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── primary.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── primaryLight.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ ├── secondary.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── secondaryLight.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ └── Images.xcassets
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── platform-logo.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── macos-logo.svg
│ │ │ └── Target+Logger.swift
│ │ ├── AuthenticationFeature
│ │ │ ├── AuthenticationReducer.swift
│ │ │ ├── AuthenticationScreen.swift
│ │ │ ├── BasicAuth
│ │ │ │ ├── BasicAuthReducer.swift
│ │ │ │ └── BasicAuthView.swift
│ │ │ ├── MFA
│ │ │ │ ├── Authentication+TemporaryFix.swift
│ │ │ │ ├── MFA6DigitsView.swift
│ │ │ │ └── MFAView.swift
│ │ │ ├── Profile
│ │ │ │ ├── ProfileReducer.swift
│ │ │ │ └── ProfileView.swift
│ │ │ ├── Target+Logger.swift
│ │ │ └── UserData.swift
│ │ ├── ConnectivityClient
│ │ │ ├── ConnectivityClient+Live.swift
│ │ │ └── ConnectivityClient.swift
│ │ ├── ConversationFeature
│ │ │ ├── Chat
│ │ │ │ ├── Chat.swift
│ │ │ │ ├── ChatReducer.swift
│ │ │ │ ├── ChatSessionState.swift
│ │ │ │ ├── EditMessageReducer.swift
│ │ │ │ ├── EditMessageView.swift
│ │ │ │ ├── MessageMenu.swift
│ │ │ │ ├── MessageView.swift
│ │ │ │ └── ReactionPickerView.swift
│ │ │ ├── ConversationScreen.swift
│ │ │ ├── ConversationScreenReducer.swift
│ │ │ ├── InfoSidebar
│ │ │ │ ├── ConversationInfoReducer.swift
│ │ │ │ ├── ConversationInfoView.swift
│ │ │ │ └── Views
│ │ │ │ │ ├── ActionRow.swift
│ │ │ │ │ ├── EntryRowLabelStyle.swift
│ │ │ │ │ ├── IdentityPopover.swift
│ │ │ │ │ └── SubtitledActionButtonStyle.swift
│ │ │ ├── MessageBar
│ │ │ │ ├── MessageBar.swift
│ │ │ │ ├── MessageBarReducer.swift
│ │ │ │ ├── MessageField
│ │ │ │ │ ├── MessageField.swift
│ │ │ │ │ └── MessageFieldReducer.swift
│ │ │ │ └── TypingIndicator.swift
│ │ │ ├── README.md
│ │ │ ├── Target+Logger.swift
│ │ │ └── Toolbar
│ │ │ │ ├── Toolbar.swift
│ │ │ │ ├── ToolbarReducer.swift
│ │ │ │ └── ToolbarSecurity.swift
│ │ ├── CredentialsClient
│ │ │ ├── CredentialsClient+Live.swift
│ │ │ ├── CredentialsClient.swift
│ │ │ └── Target+Logger.swift
│ │ ├── EditProfileFeature
│ │ │ ├── AuthenticationReducer.swift
│ │ │ ├── AuthenticationView.swift
│ │ │ ├── Components
│ │ │ │ ├── ContentSection.swift
│ │ │ │ ├── SecondaryRow.swift
│ │ │ │ └── ThreeColumns.swift
│ │ │ ├── EditProfileReducer.swift
│ │ │ ├── EditProfileScreen.swift
│ │ │ ├── EncryptionReducer.swift
│ │ │ ├── EncryptionView.swift
│ │ │ ├── IdentityReducer.swift
│ │ │ ├── IdentityView.swift
│ │ │ ├── ProfileReducer.swift
│ │ │ ├── ProfileView.swift
│ │ │ ├── Sidebar
│ │ │ │ ├── Sidebar.swift
│ │ │ │ ├── SidebarHeader.swift
│ │ │ │ ├── SidebarHeaderReducer.swift
│ │ │ │ ├── SidebarReducer.swift
│ │ │ │ ├── SidebarRow.swift
│ │ │ │ └── SidebarRowReducer.swift
│ │ │ └── Target+Logger.swift
│ │ ├── JoinChatFeature
│ │ │ ├── AddMemberSheet.swift
│ │ │ └── JoinGroupSheet.swift
│ │ ├── MainScreenFeature
│ │ │ ├── MainScreenReducer.swift
│ │ │ ├── MainScreenView.swift
│ │ │ └── Target+Logger.swift
│ │ ├── Mocks
│ │ │ ├── BareJid.swift
│ │ │ ├── Contact.swift
│ │ │ ├── Message.swift
│ │ │ ├── RandomUser
│ │ │ │ ├── RandomUser+JSON.swift
│ │ │ │ ├── RandomUser.swift
│ │ │ │ └── random_user.json
│ │ │ └── SessionState.swift
│ │ ├── NotificationsClient
│ │ │ ├── NotificationsClient+Live.swift
│ │ │ ├── NotificationsClient+Noop.swift
│ │ │ ├── NotificationsClient.swift
│ │ │ └── Target+Logger.swift
│ │ ├── PasteboardClient
│ │ │ ├── PasteboardClient+Live.swift
│ │ │ └── PasteboardClient.swift
│ │ ├── PreviewAssets
│ │ │ ├── Bundle+Fix.swift
│ │ │ ├── Generated
│ │ │ │ ├── .gitkeep
│ │ │ │ └── Assets+Generated.swift
│ │ │ ├── ImageAsset+URL.swift
│ │ │ ├── Resources
│ │ │ │ └── Assets.xcassets
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── avatars
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── alexandre.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatar.png
│ │ │ │ │ ├── antoine.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatar.jpg
│ │ │ │ │ ├── baptiste.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatar.jpg
│ │ │ │ │ ├── camille.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatag.jpg
│ │ │ │ │ ├── constellation-health.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── constellation-health.jpg
│ │ │ │ │ ├── eliott.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatar.jpg
│ │ │ │ │ ├── julien.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── julien.jpg
│ │ │ │ │ └── valerian.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── avatar.jpg
│ │ │ │ │ ├── logo-crisp.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── logo-crisp.png
│ │ │ │ │ ├── logo-makair.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── logo-makair.png
│ │ │ │ │ └── webcam-valerian.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── webcam.jpg
│ │ │ └── Target+Logger.swift
│ │ ├── ProseCore
│ │ │ ├── AccountBookmarksClient+Live.swift
│ │ │ ├── AccountBookmarksClient.swift
│ │ │ ├── AccountsClient+Live.swift
│ │ │ ├── AccountsClient.swift
│ │ │ ├── Exports.swift
│ │ │ ├── ProseCoreClient+Live.swift
│ │ │ └── ProseCoreClient.swift
│ │ ├── ProseCoreViews
│ │ │ ├── API
│ │ │ │ ├── MessagingContext.swift
│ │ │ │ └── MessagingStore.swift
│ │ │ ├── FFI.swift
│ │ │ ├── Generated
│ │ │ │ ├── .gitkeep
│ │ │ │ └── Files+Generated.swift
│ │ │ ├── Helpers
│ │ │ │ ├── Bundle+Fix.swift
│ │ │ │ ├── IdentifiedArray+Difference.swift
│ │ │ │ ├── JSEventError.swift
│ │ │ │ ├── JSHelpers.swift
│ │ │ │ ├── NSError+JavaScript.swift
│ │ │ │ └── WKUserContentController+TCA.swift
│ │ │ ├── Resources
│ │ │ │ ├── .gitkeep
│ │ │ │ └── Views
│ │ │ │ │ ├── .gitkeep
│ │ │ │ │ ├── action-more.77dcdfab.svg
│ │ │ │ │ ├── action-reactions.7a469ec6.svg
│ │ │ │ │ ├── file-other-option-get.a1a4420f.svg
│ │ │ │ │ ├── messaging.3477b7db.js
│ │ │ │ │ ├── messaging.36ff8472.css
│ │ │ │ │ ├── messaging.html
│ │ │ │ │ └── origin-attribute-insecure.aa6021b6.svg
│ │ │ ├── Target+Logger.swift
│ │ │ └── Types
│ │ │ │ ├── ColorScheme.swift
│ │ │ │ ├── EventOrigin.swift
│ │ │ │ ├── MessageAction.swift
│ │ │ │ └── MessageEvent.swift
│ │ ├── ProseUI
│ │ │ ├── AvailabilityIndicator.swift
│ │ │ ├── Avatar.swift
│ │ │ ├── Common
│ │ │ │ ├── ColoredIconLabelStyle.swift
│ │ │ │ ├── ContentCommonNameStatusComponent.swift
│ │ │ │ ├── ShadowedButtonStyle.swift
│ │ │ │ └── VerticalLabelStyle.swift
│ │ │ ├── HelpButton.swift
│ │ │ ├── Icon.swift
│ │ │ ├── LEDIndicator.swift
│ │ │ ├── LevelIndicator.swift
│ │ │ ├── OnlineStatusIndicator.swift
│ │ │ ├── ReactionPicker
│ │ │ │ ├── ReactionPicker.swift
│ │ │ │ └── ReactionPickerReducer.swift
│ │ │ ├── SpotlightGroupBoxStyle.swift
│ │ │ ├── TableFooterButton.swift
│ │ │ ├── Target+Logger.swift
│ │ │ ├── Toolbar
│ │ │ │ ├── CommonToolbar.swift
│ │ │ │ ├── CommonToolbarActions.swift
│ │ │ │ ├── CommonToolbarNavigation.swift
│ │ │ │ └── ToolbarDivider.swift
│ │ │ ├── View+OnKeyDown.swift
│ │ │ └── ViewWrap+Extension.swift
│ │ ├── SettingsFeature
│ │ │ ├── AccountSettings
│ │ │ │ ├── AccountSettingsAccountView.swift
│ │ │ │ ├── AccountSettingsFeaturesView.swift
│ │ │ │ └── AccountSettingsSecurityView.swift
│ │ │ ├── Atoms
│ │ │ │ ├── AccountPickerRow.swift
│ │ │ │ ├── ConnectionStatusIndicator.swift
│ │ │ │ ├── FormGroupBoxStyle.swift
│ │ │ │ └── VideoPreviewView.swift
│ │ │ ├── SettingsConstants.swift
│ │ │ ├── SettingsView.swift
│ │ │ ├── Tabs
│ │ │ │ ├── AccountsTab.swift
│ │ │ │ ├── AdvancedTab.swift
│ │ │ │ ├── CallsTab.swift
│ │ │ │ ├── GeneralTab.swift
│ │ │ │ ├── MessagesTab.swift
│ │ │ │ └── NotificationsTab.swift
│ │ │ └── Target+Logger.swift
│ │ ├── SidebarFeature
│ │ │ ├── Atoms
│ │ │ │ └── Counter.swift
│ │ │ ├── Footer
│ │ │ │ ├── AccountSettingsMenu
│ │ │ │ │ ├── AccountSettingsMenuReducer.swift
│ │ │ │ │ └── AccountSettingsMenuView.swift
│ │ │ │ ├── AccountSwitcherMenu
│ │ │ │ │ ├── AccountSwitcherMenuReducer.swift
│ │ │ │ │ └── AccountSwitcherMenuView.swift
│ │ │ │ ├── FooterDetails.swift
│ │ │ │ ├── FooterReducer.swift
│ │ │ │ ├── FooterView.swift
│ │ │ │ └── Styles
│ │ │ │ │ ├── SidebarFooterPopoverButtonStyle.swift
│ │ │ │ │ └── VStackGroupBoxStyle.swift
│ │ │ ├── Rows
│ │ │ │ ├── ActionButton.swift
│ │ │ │ ├── ContactRow.swift
│ │ │ │ └── IconRow.swift
│ │ │ ├── SidebarReducer.swift
│ │ │ ├── SidebarView.swift
│ │ │ └── Target+Logger.swift
│ │ ├── TestHelpers
│ │ │ ├── Date+YMD.swift
│ │ │ ├── UUID+Incrementing.swift
│ │ │ └── XCTestCase+Combine.swift
│ │ ├── TestHostApp
│ │ │ ├── Helpers
│ │ │ │ ├── Target+Logger.swift
│ │ │ │ └── TestScene.swift
│ │ │ ├── Mocks
│ │ │ │ └── AppState.swift
│ │ │ └── TestCases
│ │ │ │ └── RosterSelection.swift
│ │ ├── Toolbox
│ │ │ ├── AsyncStream+Prose.swift
│ │ │ ├── FocusState+Synchronize.swift
│ │ │ ├── ItemProvider+Prose.swift
│ │ │ ├── PlatformImage.swift
│ │ │ └── Target+Logger.swift
│ │ └── UnreadFeature
│ │ │ ├── Target+Logger.swift
│ │ │ ├── Toolbar.swift
│ │ │ ├── UnreadScreen.swift
│ │ │ ├── UnreadScreenReducer.swift
│ │ │ └── UnreadSection.swift
│ └── Tests
│ │ ├── ConversationFeatureTests
│ │ ├── Helpers
│ │ │ └── ChatSessionState+Mock.swift
│ │ └── TypingIndicatorTests.swift
│ │ ├── CredentialsClientTests
│ │ └── CredentialsClientTests.swift
│ │ └── ProseCoreViewsTests
│ │ ├── FFITests.swift
│ │ └── IdentifiedArrayDifferenceTests.swift
├── ProseUITests
│ ├── ConversationInfoTests.swift
│ ├── Helpers
│ │ └── XCUIApplication+Prose.swift
│ └── RosterSelectionTests.swift
└── UITestHost
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ ├── UITestHost.entitlements
│ └── UITestHostApp.swift
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | Prose/ProseLib/Sources/ProseCoreViews/Generated/** linguist-generated=true
2 | Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/** linguist-generated=true
3 | Prose/ProseLib/Sources/Assets/Generated/** linguist-generated=true
4 | Prose/ProseLib/Sources/AppLocalization/Generated/** linguist-generated=true
5 | Prose/ProseLib/Sources/PreviewAssets/Generated/** linguist-generated=true
6 |
--------------------------------------------------------------------------------
/.github/workflows/delete_old_workflow_runs.yml:
--------------------------------------------------------------------------------
1 | name: Delete old workflow runs
2 | on:
3 | schedule:
4 | - cron: '0 0 1 * *'
5 | # Run monthly, at 00:00 on the 1st day of month.
6 |
7 | jobs:
8 | del_runs:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Delete old workflow runs
12 | uses: Mattraks/delete-workflow-runs@v2
13 | with:
14 | token: ${{ github.token }}
15 | repository: ${{ github.repository }}
16 | retain_days: 30
17 | keep_minimum_runs: 6
18 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.6.1
2 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --indent 2
2 | --self insert
3 | --disable enumNamespaces
4 | --enable isEmpty,blockComments
5 | --ranges no-space
6 | --decimalgrouping 3,5
7 | --wraparguments before-first
8 | --wrapcollections before-first
9 | --maxwidth 100
10 | --header \nThis file is part of prose-app-macos.\nCopyright (c) 2023 Prose Foundation\n
--------------------------------------------------------------------------------
/BuildTools/Empty.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | // File intentionally left empty.
7 | // https://blog.apptekstudios.com/2019/12/spm-xcode-build-tools/
8 |
--------------------------------------------------------------------------------
/BuildTools/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.6
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "BuildTools",
6 | platforms: [.macOS(.v10_11)],
7 | dependencies: [
8 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.9"),
9 | .package(url: "https://github.com/SwiftGen/SwiftGen", from: "6.5.1"),
10 | .package(url: "https://github.com/thii/xcbeautify", from: "0.13.0"),
11 | ],
12 | targets: [.target(name: "BuildTools", path: "")]
13 | )
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog — Prose (macOS)
2 |
3 | ## 0.1.0 (unreleased)
4 |
5 | * Initial version.
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # ################ Assets ################
2 |
3 | assets: swiftgen format
4 |
5 | swiftgen:
6 | @(cd BuildTools; SDKROOT=macosx; swift run -c release swiftgen config run --config ./swiftgen.yml)
7 |
8 | format:
9 | @(cd BuildTools; SDKROOT=macosx; swift run -c release swiftformat ..)
10 |
11 | XCBEAUTIFY:
12 | @(cd BuildTools; SDKROOT=macosx; echo "" | swift run -c release xcbeautify)
13 |
14 | # ################ Web Views ################
15 |
16 | VIEWS_LIB_URL=https://github.com/prose-im/prose-core-views
17 | VIEWS_LIB_VERSION=0.17.0
18 | VIEWS_ARCHIVE_NAME=release-${VIEWS_LIB_VERSION}.tar.gz
19 | DESTINATION=Prose/ProseLib/Sources/ProseCoreViews/Resources/Views
20 |
21 | views: views-build assets
22 |
23 | views-build:
24 | rm -rf "${DESTINATION}"
25 | mkdir "${DESTINATION}"
26 | touch "${DESTINATION}/.gitkeep"
27 |
28 | @curl -Ls "${VIEWS_LIB_URL}/releases/download/${VIEWS_LIB_VERSION}/${VIEWS_ARCHIVE_NAME}" | tar -xvz -C ${DESTINATION} --strip=1
29 |
30 | # ################ Code Hygiene ################
31 |
32 | XCBEAUTIFY = ./BuildTools/.build/release/xcbeautify
33 | XCODEBUILD = set -o pipefail && xcodebuild
34 | XCPROJ = Prose/Prose.xcodeproj
35 | XCSCHEME = Prose
36 | PREVIEW_SCHEMES = ConversationFeaturePreview EditProfileFeaturePreview
37 |
38 | preflight: lint test release_build build_preview_apps
39 |
40 | lint:
41 | @(cd BuildTools; SDKROOT=macosx; swift run -c release swiftformat --lint ..)
42 |
43 | test: XCBEAUTIFY
44 | @$(XCODEBUILD) \
45 | -project $(XCPROJ) \
46 | -scheme $(XCSCHEME) \
47 | -testPlan AllTests \
48 | test | $(XCBEAUTIFY)
49 |
50 | test-ci: XCBEAUTIFY
51 | @$(XCODEBUILD) \
52 | -project $(XCPROJ) \
53 | -scheme $(XCSCHEME) \
54 | -testPlan AllTests \
55 | -resultBundlePath TestResults \
56 | test | $(XCBEAUTIFY)
57 |
58 | release_build: XCBEAUTIFY
59 | @(export IS_RELEASE_BUILD=1 && $(XCODEBUILD) \
60 | -project $(XCPROJ) \
61 | -scheme $(XCSCHEME) \
62 | -configuration Release | $(XCBEAUTIFY))
63 |
64 | build_preview_apps: XCBEAUTIFY $(PREVIEW_SCHEMES)
65 |
66 | $(PREVIEW_SCHEMES): XCBEAUTIFY
67 | @$(XCODEBUILD) \
68 | -project $(XCPROJ) \
69 | -scheme $@ | $(XCBEAUTIFY)
70 |
--------------------------------------------------------------------------------
/Prose/.gitignore:
--------------------------------------------------------------------------------
1 | Prose.xcodeproj/**/*.xcuserdatad/
2 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/ConversationFeaturePreview.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/ConversationFeaturePreviewApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | @main
9 | struct ConversationFeaturePreviewApp: App {
10 | var body: some Scene {
11 | WindowGroup {
12 | ContentView()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Prose/ConversationFeaturePreview/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/EditProfileFeaturePreview/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/EditProfileFeaturePreview/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/EditProfileFeaturePreview/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/EditProfileFeaturePreview/EditProfileFeaturePreview.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Prose/EditProfileFeaturePreview/EditProfileFeaturePreviewApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | @testable import EditProfileFeature
8 | import Mocks
9 | import ProseCore
10 | import SwiftUI
11 |
12 | @main
13 | struct EditProfileFeaturePreviewApp: App {
14 | let store: StoreOf
15 |
16 | init() {
17 | var state = EditProfileReducer.EditProfileState()
18 | state.route = .profile(.init())
19 |
20 | self.store = Store(
21 | initialState: .mock(state),
22 | reducer: EditProfileReducer(),
23 | prepareDependencies: {
24 | var accountsClient = AccountsClient.noop
25 | accountsClient.client = { _ in .noop }
26 | $0.accountsClient = accountsClient
27 | }
28 | )
29 | }
30 |
31 | var body: some Scene {
32 | WindowGroup {
33 | EditProfileScreen(store: self.store)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Prose/Prose.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Prose/Prose.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Prose/Prose/AllTests.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "AA58C178-605B-4A55-B48D-7D8B07E8DD8D",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "language" : "en",
13 | "region" : "US",
14 | "testTimeoutsEnabled" : true
15 | },
16 | "testTargets" : [
17 | {
18 | "skippedTests" : [
19 | "CredentialsStoreTests"
20 | ],
21 | "target" : {
22 | "containerPath" : "container:ProseLib",
23 | "identifier" : "CredentialsClientTests",
24 | "name" : "CredentialsClientTests"
25 | }
26 | },
27 | {
28 | "target" : {
29 | "containerPath" : "container:Prose.xcodeproj",
30 | "identifier" : "2CBFFE66287D7B0B00A53992",
31 | "name" : "ProseUITests"
32 | }
33 | },
34 | {
35 | "target" : {
36 | "containerPath" : "container:ProseLib",
37 | "identifier" : "ProseCoreViewsTests",
38 | "name" : "ProseCoreViewsTests"
39 | }
40 | },
41 | {
42 | "target" : {
43 | "containerPath" : "container:ProseLib",
44 | "identifier" : "ConversationFeatureTests",
45 | "name" : "ConversationFeatureTests"
46 | }
47 | }
48 | ],
49 | "version" : 1
50 | }
51 |
--------------------------------------------------------------------------------
/Prose/Prose/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
--------------------------------------------------------------------------------
/Prose/Prose/Components/Sidebar/SidebarPartContextComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct SidebarPartContextComponent: View {
9 | var avatar: String = "avatar-valerian"
10 | var teamName: String = "Crisp"
11 | var statusIcon: Character = "🚀"
12 | var statusMessage: String = "Building new stuff."
13 |
14 | var body: some View {
15 | VStack(spacing: 0) {
16 | Divider()
17 |
18 | HStack(spacing: 12) {
19 | // User avatar
20 | SidebarContextAvatarComponent(
21 | avatar: self.avatar,
22 | status: .online
23 | )
24 |
25 | // Team name + user status
26 | SidebarContextCurrentComponent(
27 | teamName: self.teamName,
28 | statusIcon: self.statusIcon,
29 | statusMessage: self.statusMessage
30 | )
31 | .layoutPriority(1)
32 |
33 | Spacer()
34 |
35 | // Quick action button
36 | SidebarContextActionsComponent()
37 | }
38 | .padding(.leading, 20.0)
39 | .padding(.trailing, 14.0)
40 | .frame(maxHeight: 64)
41 | }
42 | .frame(height: 64)
43 | }
44 | }
45 |
46 | struct SidebarPartContextComponent_Previews: PreviewProvider {
47 | static var previews: some View {
48 | SidebarPartContextComponent()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Prose/Prose/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Preferences",
6 | "repositoryURL": "https://github.com/sindresorhus/Preferences.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "ffeaaad1def45d0625720dc1adae3789cd9c167d",
10 | "version": "2.5.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Prose/Prose/Prose.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Prose/Prose/ProseApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import App
7 | import SwiftUI
8 |
9 | @main
10 | struct ProseApp: App {
11 | var body: some Scene {
12 | AppScene()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Dock@16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "Dock@32 1.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "Dock@32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "Dock@64.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "Dock@128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "Dock@256 1.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "Dock@256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "Dock@512 1.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "Dock@512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "Dock@1024.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@1024.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@128.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@16.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@256 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@256 1.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@256.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@32 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@32 1.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@32.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@512 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@512 1.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@512.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/Prose/Resources/Assets.xcassets/AppIcon.appiconset/Dock@64.png
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/Prose/Resources/Credits.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf2636
2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 LucidaGrande;
3 | }
4 | {\colortbl;\red255\green255\blue255;}
5 | {\*\expandedcolortbl;;}
6 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid1\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}}
7 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
8 | \margl1440\margr1440\vieww9000\viewh8400\viewkind0
9 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0
10 |
11 | \f0\b\fs24 \cf0 Developer:
12 | \f1\b0 Valerian Saliou\
13 | \
14 |
15 | \f0\b Open-Source libraries:
16 | \f1\b0 \
17 | \
18 | \pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5492\tx5669\tx6236\tx6803\li720\fi-720\pardirnatural\partightenfactor0
19 | \ls1\ilvl0\cf0
20 | \f2 \uc0\u9642
21 | \f1 Preferences by @sindresorhus}
--------------------------------------------------------------------------------
/Prose/Prose/UITests.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "0D940990-E17C-4D24-AEBC-3F70CD687104",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "language" : "en",
13 | "region" : "US",
14 | "testTimeoutsEnabled" : true
15 | },
16 | "testTargets" : [
17 | {
18 | "target" : {
19 | "containerPath" : "container:Prose.xcodeproj",
20 | "identifier" : "2CBFFE66287D7B0B00A53992",
21 | "name" : "ProseUITests"
22 | }
23 | }
24 | ],
25 | "version" : 1
26 | }
27 |
--------------------------------------------------------------------------------
/Prose/Prose/UnitTests.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "AA58C178-605B-4A55-B48D-7D8B07E8DD8D",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "language" : "en",
13 | "region" : "US",
14 | "testTimeoutsEnabled" : true
15 | },
16 | "testTargets" : [
17 | {
18 | "target" : {
19 | "containerPath" : "container:ProseLib",
20 | "identifier" : "CredentialsClientTests",
21 | "name" : "CredentialsClientTests"
22 | }
23 | },
24 | {
25 | "target" : {
26 | "containerPath" : "container:ProseLib",
27 | "identifier" : "ProseCoreViewsTests",
28 | "name" : "ProseCoreViewsTests"
29 | }
30 | },
31 | {
32 | "target" : {
33 | "containerPath" : "container:ProseLib",
34 | "identifier" : "ConversationFeatureTests",
35 | "name" : "ConversationFeatureTests"
36 | }
37 | }
38 | ],
39 | "version" : 1
40 | }
41 |
--------------------------------------------------------------------------------
/Prose/ProseLib/.gitignore:
--------------------------------------------------------------------------------
1 | .env.json
--------------------------------------------------------------------------------
/Prose/ProseLib/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AddressBookFeature/AddressBookScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct AddressBookScreen: View {
9 | public init() {}
10 |
11 | public var body: some View {
12 | Text("Address book")
13 | .frame(maxWidth: .infinity, maxHeight: .infinity)
14 | .unredacted()
15 | .toolbar(content: Toolbar.init)
16 | }
17 | }
18 |
19 | internal struct AddressBookScreen_Previews: PreviewProvider {
20 | private struct Preview: View {
21 | var body: some View {
22 | NavigationView {
23 | Text("Test")
24 | AddressBookScreen()
25 | }
26 | }
27 | }
28 |
29 | static var previews: some View {
30 | Preview()
31 | Preview()
32 | .redacted(reason: .placeholder)
33 | .previewDisplayName("Placeholder")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AddressBookFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "address-book")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AddressBookFeature/Toolbar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseUI
7 | import SwiftUI
8 |
9 | struct Toolbar: ToolbarContent {
10 | var body: some ToolbarContent {
11 | ToolbarItemGroup(placement: .navigation) {
12 | CommonToolbarNavigation()
13 | }
14 |
15 | ToolbarItemGroup {
16 | Self.actions()
17 |
18 | ToolbarDivider()
19 |
20 | CommonToolbarActions()
21 | }
22 | }
23 |
24 | static func actions() -> some View {
25 | Group {
26 | Button { logger.info("Add contact tapped") } label: {
27 | Label("Add contact", systemImage: "person.crop.circle.badge.plus")
28 | }
29 | Button { logger.info("Stack plus tapped") } label: {
30 | Label("Add group", systemImage: "rectangle.stack.badge.plus")
31 | }
32 |
33 | ToolbarDivider()
34 |
35 | Menu {
36 | // TODO: Add actions
37 | Text("TODO")
38 | } label: {
39 | Label("Filter", systemImage: "line.3.horizontal.decrease.circle")
40 | }
41 | }
42 | .unredacted()
43 | }
44 | }
45 |
46 | internal struct Toolbar_Previews: PreviewProvider {
47 | private struct Preview: View {
48 | var body: some View {
49 | HStack {
50 | Toolbar.actions()
51 | }
52 | }
53 | }
54 |
55 | static var previews: some View {
56 | Preview()
57 | Preview()
58 | .redacted(reason: .placeholder)
59 | .previewDisplayName("Placeholder")
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/App/Accounts/SelectedAccountReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import ProseCore
8 |
9 | private extension AppReducer.State {
10 | var selectedAccount: Account? {
11 | self.selectedAccountId.flatMap {
12 | self.availableAccounts[id: $0]
13 | }
14 | }
15 | }
16 |
17 | // Observes changes to the selected account
18 | struct SelectedAccountReducer<
19 | Base: ReducerProtocol
20 | >: ReducerProtocol {
21 | let base: Base
22 |
23 | @Dependency(\.accountsClient) var accounts
24 |
25 | public var body: some ReducerProtocol {
26 | // Make sure that the selected account actually changed and not that the selection was replaced
27 | self.base
28 | .onChange(of: \.selectedAccount) { formerAccount, currentAccount, _, _ in
29 | guard
30 | let formerAccount,
31 | let currentAccount,
32 | formerAccount.jid == currentAccount.jid
33 | else {
34 | return .none
35 | }
36 |
37 | var effects = [EffectTask]()
38 |
39 | if currentAccount.availability != formerAccount.availability {
40 | effects.append(.fireAndForget {
41 | try await self.accounts.client(currentAccount.jid)
42 | .setAvailability(currentAccount.availability, nil)
43 | })
44 | }
45 |
46 | if currentAccount.settings != formerAccount.settings {
47 | effects.append(.fireAndForget {
48 | try await self.accounts.client(currentAccount.jid)
49 | .saveAccountSettings(currentAccount.settings)
50 | })
51 | }
52 |
53 | return .merge(effects)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppKit
7 |
8 | final class AppDelegate: NSObject, NSApplicationDelegate {
9 | func applicationDidFinishLaunching(_: Notification) {
10 | NSWindow.allowsAutomaticWindowTabbing = false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/App/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "app")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/Account.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | @dynamicMemberLookup
10 | public struct Account: Hashable, Identifiable {
11 | public var jid: BareJid
12 | public var status: ConnectionStatus
13 | public var settings: AccountSettings
14 | public var profile: UserProfile?
15 | public var contacts: [BareJid: Contact]
16 | public var avatar: URL?
17 |
18 | public var id: BareJid {
19 | self.jid
20 | }
21 |
22 | public init(
23 | jid: BareJid,
24 | status: ConnectionStatus,
25 | settings: AccountSettings,
26 | profile: UserProfile? = nil,
27 | contacts: [BareJid: Contact] = [:],
28 | avatar: URL? = nil
29 | ) {
30 | self.jid = jid
31 | self.status = status
32 | self.settings = settings
33 | self.profile = profile
34 | self.contacts = contacts
35 | self.avatar = avatar
36 | }
37 | }
38 |
39 | public extension Account {
40 | subscript(dynamicMember keyPath: WritableKeyPath) -> T {
41 | get { self.settings[keyPath: keyPath] }
42 | set { self.settings[keyPath: keyPath] = newValue }
43 | }
44 | }
45 |
46 | public extension Account {
47 | var username: String {
48 | if let fullName = self.profile?.fullName {
49 | return fullName
50 | }
51 | if let nickname = self.profile?.nickname {
52 | return nickname
53 | }
54 | return (self.jid.node ?? self.jid.domain)
55 | .split(separator: ".", omittingEmptySubsequences: true)
56 | .joined(separator: " ")
57 | .localizedCapitalized
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ConnectionStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import BareMinimum
7 | import Foundation
8 |
9 | public enum ConnectionStatus: Hashable {
10 | case disconnected
11 | case connecting
12 | case connected
13 | case error(Error)
14 | }
15 |
16 | public extension ConnectionStatus {
17 | var isError: Bool {
18 | if case .error = self {
19 | return true
20 | }
21 | return false
22 | }
23 | }
24 |
25 | public extension ConnectionStatus {
26 | static func == (lhs: Self, rhs: Self) -> Bool {
27 | switch (lhs, rhs) {
28 | case (.disconnected, .disconnected):
29 | return true
30 | case (.connecting, .connecting):
31 | return true
32 | case (.connected, .connected):
33 | return true
34 | case let (.error(lErr), .error(rErr)):
35 | return lErr.isEqual(to: rErr)
36 | case (.disconnected, _), (.connecting, _), (.connected, _), (.error, _):
37 | return false
38 | }
39 | }
40 |
41 | func hash(into hasher: inout Hasher) {
42 | switch self {
43 | case .disconnected:
44 | hasher.combine(1)
45 | case .connecting:
46 | hasher.combine(2)
47 | case .connected:
48 | hasher.combine(3)
49 | case let .error(error):
50 | hasher.combine(EquatableError(error))
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/Connectivity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | public enum Connectivity: Hashable {
7 | case online
8 | case offline
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/Credentials.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | public struct Credentials: Hashable {
10 | public let jid: BareJid
11 | public let password: String
12 |
13 | public init(
14 | jid: BareJid,
15 | password: String
16 | ) {
17 | self.jid = jid
18 | self.password = password
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/Exports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | @_exported import ProseCoreFFI
7 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/Availability.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseCoreFFI
7 |
8 | public extension Availability {
9 | static let selectableCases: [Availability] = [
10 | .available,
11 | .away,
12 | .doNotDisturb,
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/BareJid.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | extension BareJid: RawRepresentable {
10 | public init?(rawValue: String) {
11 | guard let jid = try? parseJid(jid: rawValue) else {
12 | return nil
13 | }
14 | self = jid
15 | }
16 |
17 | public var rawValue: String {
18 | formatJid(jid: self)
19 | }
20 | }
21 |
22 | extension BareJid: Codable {
23 | public init(from decoder: Decoder) throws {
24 | let container = try decoder.singleValueContainer()
25 | self = try parseJid(jid: container.decode(String.self))
26 | }
27 |
28 | public func encode(to encoder: Encoder) throws {
29 | var container = encoder.singleValueContainer()
30 | try container.encode(self.rawValue)
31 | }
32 | }
33 |
34 | #if DEBUG
35 | /// Use this for testing only, since we might crash otherwise.
36 | extension BareJid: ExpressibleByStringLiteral {
37 | public init(stringLiteral value: StringLiteralType) {
38 | do {
39 | try self = parseJid(jid: value)
40 | } catch {
41 | fatalError(error.localizedDescription)
42 | }
43 | }
44 | }
45 | #endif
46 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/Contact.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseCoreFFI
7 |
8 | extension Contact: Comparable {
9 | public static func < (lhs: Contact, rhs: Contact) -> Bool {
10 | switch lhs.name.localizedStandardCompare(rhs.name) {
11 | case .orderedAscending:
12 | return true
13 | case .orderedDescending:
14 | return false
15 | case .orderedSame:
16 | return lhs.jid.rawValue.caseInsensitiveCompare(rhs.jid.rawValue) == .orderedAscending
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/FullJid.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | public extension FullJid {
10 | var bareJid: BareJid {
11 | BareJid(node: self.node, domain: self.domain)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/Message.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | extension Message: Identifiable {}
10 |
11 | extension Message: Encodable {
12 | enum CodingKeys: String, CodingKey {
13 | case id
14 | case type
15 | case date
16 | case content
17 | case from
18 | case reactions
19 | case metas
20 | }
21 |
22 | enum UserCodingKeys: String, CodingKey {
23 | case jid, name
24 | }
25 |
26 | enum MetaCodingKeys: String, CodingKey {
27 | case encrypted, edited
28 | }
29 |
30 | fileprivate static var dateFormatter: ISO8601DateFormatter = {
31 | let formatter = ISO8601DateFormatter()
32 | formatter.formatOptions.insert(.withFractionalSeconds)
33 | return formatter
34 | }()
35 |
36 | public func encode(to encoder: Encoder) throws {
37 | var container = encoder.container(keyedBy: CodingKeys.self)
38 |
39 | try container.encode(self.id, forKey: .id)
40 | try container.encode("text", forKey: .type)
41 | try container.encode(Self.dateFormatter.string(from: self.timestamp), forKey: .date)
42 | try container.encode(self.body, forKey: .content)
43 | try container.encode(self.from, forKey: .from)
44 |
45 | do {
46 | var container = container.nestedContainer(keyedBy: MetaCodingKeys.self, forKey: .metas)
47 | try container.encode(self.isEdited, forKey: .edited)
48 | try container.encode(false, forKey: .encrypted)
49 | }
50 |
51 | try container.encode(self.reactions, forKey: .reactions)
52 | }
53 | }
54 |
55 | extension Reaction: Encodable {
56 | enum CodingKeys: String, CodingKey {
57 | case emoji = "reaction"
58 | case from = "authors"
59 | }
60 |
61 | public func encode(to encoder: Encoder) throws {
62 | var container = encoder.container(keyedBy: CodingKeys.self)
63 | try container.encode(self.emoji, forKey: .emoji)
64 | try container.encode(self.from, forKey: .from)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/ProseCoreExtensions/UserProfile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseCoreFFI
7 |
8 | public extension UserProfile {
9 | init() {
10 | self = .init(
11 | fullName: nil,
12 | nickname: nil,
13 | org: nil,
14 | title: nil,
15 | email: nil,
16 | tel: nil,
17 | url: nil,
18 | address: nil
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppDomain/UserInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import ProseCoreFFI
8 |
9 | public struct UserInfo: Equatable {
10 | public var jid: BareJid
11 | public var name: String
12 | public var avatar: URL?
13 |
14 | public init(jid: BareJid, name: String, avatar: URL? = nil) {
15 | self.jid = jid
16 | self.name = name
17 | self.avatar = avatar
18 | }
19 | }
20 |
21 | public extension UserInfo {
22 | init(contact: Contact) {
23 | self.jid = contact.jid
24 | self.name = contact.name
25 | self.avatar = contact.avatar
26 | }
27 | }
28 |
29 | extension UserInfo: Encodable {}
30 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppLocalization/AppDomain+i18n.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 |
8 | public extension Availability {
9 | var localizedDescription: String {
10 | switch self {
11 | case .available:
12 | return L10n.Availability.available
13 | case .unavailable:
14 | return L10n.Availability.unavailable
15 | case .doNotDisturb:
16 | return L10n.Availability.doNotDisturb
17 | case .away:
18 | return L10n.Availability.away
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppLocalization/Generated/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/AppLocalization/Generated/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppLocalization/Resources/en.lproj/Localizable.stringsdict:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | content.message_bar.typing
6 |
7 | NSStringLocalizedFormatKey
8 | %#@typing@
9 | typing
10 |
11 | NSStringFormatSpecTypeKey
12 | NSStringPluralRuleType
13 | NSStringFormatValueTypeKey
14 | u
15 | one
16 | is typing…
17 | other
18 | are typing…
19 |
20 |
21 | content.message_bar.others
22 |
23 | NSStringLocalizedFormatKey
24 | %#@others@
25 | others
26 |
27 | NSStringFormatSpecTypeKey
28 | NSStringPluralRuleType
29 | NSStringFormatValueTypeKey
30 | u
31 | one
32 | %u other
33 | other
34 | %u others
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppLocalization/String+Markdown.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public extension String {
9 | /// Convert a `String` to a Markdown `AttributedString`.
10 | ///
11 | /// SwiftUI automatically renders Markdown, but only for static strings used in the `Text`
12 | /// initializer.
13 | /// If a string is localized, it doesn't render Markdown.
14 | /// That's why we have to use:
15 | ///
16 | /// ```swift
17 | /// Text(L10n.myString.asMarkdown)
18 | /// ```
19 | var asMarkdown: AttributedString {
20 | do {
21 | return try AttributedString(
22 | markdown: self,
23 | options: AttributedString.MarkdownParsingOptions(
24 | // Render `\n`s as new lines.
25 | interpretedSyntax: .inlineOnlyPreservingWhitespace
26 | )
27 | )
28 | } catch {
29 | logger.warning("Error parsing markdown: \(error.localizedDescription)")
30 | return AttributedString(self)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AppLocalization/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "localization")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Generated/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/Assets/Generated/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/ImageAsset+URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | /// `PreviewAssets.ImageAsset.customURL` creates a custom URL for assets, using the `asset` scheme.
9 | /// This function tries to parse an URL to check if it's an asset.
10 | ///
11 | /// ```swift
12 | /// let url: URL? = URL(string: "asset://path/to.bundle?imageName=some-image-asset")
13 | /// if let assetData = url.flatMap(Assets.assetData) {
14 | /// Image(assetData.0, bundle: assetData.1).resizable()
15 | /// } else {
16 | /// AsyncImage(url: url) { image in
17 | /// image.resizable()
18 | /// } placeholder: {
19 | /// Color.primary.opacity(0.125)
20 | /// }
21 | /// }
22 | /// ```
23 | ///
24 | /// - Returns: If the URL references an asset, it returns the asset name (potentially namespaced)
25 | /// and the optional `Bundle`. `nil` otherwise or if any error occured.
26 | public func assetData(from url: URL) -> (String, Bundle?)? {
27 | guard url.scheme == "asset" else { return nil }
28 | guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
29 | else { return nil }
30 |
31 | let query = components.queryItems ?? []
32 | components.queryItems = nil
33 |
34 | guard let imageName = query.first(where: { $0.name == "imageName" })?.value else { return nil }
35 |
36 | return (imageName, Bundle(path: components.path))
37 | }
38 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/background/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/background/message.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.000",
27 | "green" : "0.000",
28 | "red" : "0.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/border/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/border/primary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.514"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/border/secondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.741"
9 | }
10 | },
11 | "idiom" : "universal"
12 | },
13 | {
14 | "appearances" : [
15 | {
16 | "appearance" : "luminosity",
17 | "value" : "dark"
18 | }
19 | ],
20 | "color" : {
21 | "color-space" : "extended-gray",
22 | "components" : {
23 | "alpha" : "0.750",
24 | "white" : "0.502"
25 | }
26 | },
27 | "idiom" : "universal"
28 | }
29 | ],
30 | "info" : {
31 | "author" : "xcode",
32 | "version" : 1
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/border/tertiary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.902"
9 | }
10 | },
11 | "idiom" : "universal"
12 | },
13 | {
14 | "appearances" : [
15 | {
16 | "appearance" : "luminosity",
17 | "value" : "dark"
18 | }
19 | ],
20 | "color" : {
21 | "color-space" : "extended-gray",
22 | "components" : {
23 | "alpha" : "0.750",
24 | "white" : "0.376"
25 | }
26 | },
27 | "idiom" : "universal"
28 | }
29 | ],
30 | "info" : {
31 | "author" : "xcode",
32 | "version" : 1
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/border/tertiaryLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.827"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/coolGrey.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x7B",
9 | "green" : "0x61",
10 | "red" : "0x54"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/green.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.494",
9 | "green" : "0.671",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/greenLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.329",
9 | "green" : "0.780",
10 | "red" : "0.380"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/grey.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.475"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/state/greyLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.733"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/text/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/text/primary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.149",
9 | "green" : "0.145",
10 | "red" : "0.137"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "platform" : "osx",
24 | "reference" : "labelColor"
25 | },
26 | "idiom" : "universal"
27 | }
28 | ],
29 | "info" : {
30 | "author" : "xcode",
31 | "version" : 1
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/text/primaryLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.302"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/text/secondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.475"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Colors.xcassets/text/secondaryLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "extended-gray",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "white" : "0.502"
9 | }
10 | },
11 | "idiom" : "universal"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Resources/Images.xcassets/platform-logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "macos-logo.svg",
5 | "idiom" : "mac"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "original"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Assets/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "assets")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AuthenticationFeature/MFA/Authentication+TemporaryFix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import Foundation
8 |
9 | public enum MFAError: Error, Equatable {
10 | case badCode
11 | }
12 |
13 | extension MFAError: LocalizedError {
14 | public var errorDescription: String? {
15 | switch self {
16 | case .badCode:
17 | return "Bad code"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AuthenticationFeature/MFA/MFAView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import CredentialsClient
8 | import SwiftUI
9 |
10 | // Let's leave this here as-is until we have MFA server-side support
11 |
12 | // struct MFAView: View {
13 | // typealias State = MFAState
14 | // typealias Action = MFAAction
15 | //
16 | // let store: Store
17 | // private var actions: ViewStore { ViewStore(self.store.stateless) }
18 | //
19 | // var body: some View {
20 | // SwitchStore(self.store) {
21 | // CaseLet(
22 | // state: CasePath(State.sixDigits).extract(from:),
23 | // action: Action.sixDigits,
24 | // then: MFA6DigitsView.init(store:)
25 | // )
26 | // }
27 | // }
28 | // }
29 | //
30 | //// MARK: - The Composable Architecture
31 | //
32 | //// MARK: Reducer
33 | //
34 | // struct AuthenticationEnvironment {
35 | // var proseClient: ProseClient
36 | // var credentials: CredentialsClient
37 | // var mainQueue: AnySchedulerOf
38 | // }
39 | //
40 | // let mfaReducer: AnyReducer<
41 | // MFAState,
42 | // MFAAction,
43 | // AuthenticationEnvironment
44 | // > = AnyReducer.combine([
45 | // mfa6DigitsReducer.pullback(
46 | // state: CasePath(MFAState.sixDigits),
47 | // action: CasePath(MFAAction.sixDigits),
48 | // environment: { $0 }
49 | // ),
50 | // AnyReducer { _, action, _ in
51 | // switch action {
52 | // case let .sixDigits(.verifyOneTimeCodeResult(.success(route))):
53 | // return EffectTask(value: .didPassChallenge(next: route))
54 | //
55 | // default:
56 | // break
57 | // }
58 | //
59 | // return .none
60 | // },
61 | // ])
62 | //
63 | //// MARK: State
64 | //
65 | // public enum MFAState: Equatable {
66 | // case sixDigits(MFA6DigitsState)
67 | // }
68 | //
69 | //// MARK: Actions
70 | //
71 | // public enum MFAAction: Equatable {
72 | // case didPassChallenge(next: Authentication.Route)
73 | // case sixDigits(MFA6DigitsAction)
74 | // }
75 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AuthenticationFeature/Profile/ProfileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 | import SwiftUI
9 |
10 | struct ProfileView: View {
11 | let store: StoreOf
12 |
13 | @ObservedObject var viewStore: ViewStoreOf
14 | @FocusState private var focusedField: ProfileReducer.Field?
15 |
16 | init(store: StoreOf) {
17 | self.store = store
18 | self.viewStore = ViewStore(store)
19 | }
20 |
21 | var body: some View {
22 | VStack(spacing: 32) {
23 | VStack {
24 | TextField(
25 | L10n.Authentication.Profile.Form.FullName.placeholder,
26 | text: self.viewStore.binding(\.$fullName)
27 | )
28 | .textContentType(.username)
29 | .focused(self.$focusedField, equals: .fullName)
30 | .onSubmit { self.viewStore.send(.fieldSubmitted(.fullName)) }
31 |
32 | TextField(
33 | L10n.Authentication.Profile.Form.Title.placeholder,
34 | text: self.viewStore.binding(\.$title)
35 | )
36 | .focused(self.$focusedField, equals: .title)
37 | .onSubmit { self.viewStore.send(.fieldSubmitted(.title)) }
38 | }.disabled(self.viewStore.isLoading)
39 |
40 | Button(action: { self.viewStore.send(.submitButtonTapped) }) {
41 | Text(
42 | self.viewStore.isLoading
43 | ? L10n.Authentication.Profile.Form.cancel
44 | : L10n.Authentication.Profile.Form.save
45 | )
46 | .frame(minWidth: 196)
47 | }
48 | .overlay(alignment: .leading) {
49 | if self.viewStore.isLoading {
50 | ProgressView()
51 | .scaleEffect(0.5, anchor: .center)
52 | }
53 | }
54 | }
55 | .synchronize(self.viewStore.binding(\.$focusedField), self.$focusedField)
56 | .alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AuthenticationFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "authentication")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/AuthenticationFeature/UserData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Foundation
8 |
9 | public struct UserData: Equatable {
10 | let credentials: Credentials
11 | var avatar: URL?
12 | var profile: UserProfile?
13 | }
14 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConnectivityClient/ConnectivityClient+Live.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Combine
8 | import ComposableArchitecture
9 | import Network
10 |
11 | extension ConnectivityClient {
12 | static let live: ConnectivityClient = {
13 | let connectivitySubject = CurrentValueSubject(Connectivity.online)
14 | let pathMonitorQueue = DispatchQueue(label: "org.prose.pathmonitor")
15 | let pathMonitor = NWPathMonitor()
16 |
17 | pathMonitor.pathUpdateHandler = { path in
18 | switch path.status {
19 | case .satisfied:
20 | connectivitySubject.send(.online)
21 | case .unsatisfied, .requiresConnection:
22 | connectivitySubject.send(.offline)
23 | @unknown default:
24 | connectivitySubject.send(.offline)
25 | }
26 | }
27 |
28 | pathMonitor.start(queue: pathMonitorQueue)
29 |
30 | return ConnectivityClient(connectivity: {
31 | AsyncStream(connectivitySubject.removeDuplicates().values)
32 | })
33 | }()
34 | }
35 |
36 | extension ConnectivityClient: DependencyKey {
37 | public static var liveValue = ConnectivityClient.live
38 |
39 | public static var previewValue = ConnectivityClient(
40 | connectivity: {
41 | AsyncStream(unfolding: { .online })
42 | }
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConnectivityClient/ConnectivityClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 |
9 | public struct ConnectivityClient {
10 | public var connectivity: () -> AsyncStream
11 | }
12 |
13 | public extension DependencyValues {
14 | var connectivityClient: ConnectivityClient {
15 | get { self[ConnectivityClient.self] }
16 | set { self[ConnectivityClient.self] = newValue }
17 | }
18 | }
19 |
20 | extension ConnectivityClient: TestDependencyKey {
21 | public static var testValue = ConnectivityClient(
22 | connectivity: unimplemented("\(Self.self).connectivity")
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Chat/EditMessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 | import ProseUI
9 | import SwiftUI
10 |
11 | struct EditMessageView: View {
12 | let store: StoreOf
13 | let viewStore: ViewStoreOf
14 |
15 | init(store: StoreOf) {
16 | self.store = store
17 | self.viewStore = ViewStore(store)
18 | }
19 |
20 | var body: some View {
21 | VStack(alignment: .leading) {
22 | Text(L10n.Content.EditMessage.title)
23 | .font(.title2.bold())
24 | MessageField(store: self.store.scope(
25 | state: \.messageField,
26 | action: EditMessageReducer.Action.messageField
27 | ))
28 | }
29 | .safeAreaInset(edge: .bottom, spacing: 12) {
30 | HStack {
31 | Group {
32 | Button { print("NOT IMPLEMENTED") } label: {
33 | Image(systemName: "paperclip")
34 | }
35 |
36 | Button { self.viewStore.send(.emojiButtonTapped) } label: {
37 | Image(systemName: "face.smiling")
38 | }
39 | .reactionPicker(
40 | store: self.store.scope(state: \.emojiPicker),
41 | action: EditMessageReducer.Action.emojiPicker,
42 | dismiss: .emojiPickerDismissed
43 | )
44 | }
45 | .buttonStyle(.plain)
46 | .font(MessageBar.buttonsFont)
47 | Spacer()
48 | WithViewStore(self.store.scope(state: \.childState.isConfirmButtonEnabled)) { viewStore in
49 | Button(L10n.Content.EditMessage.CancelAction.title, role: .cancel) {
50 | viewStore.send(.cancelTapped)
51 | }
52 | .buttonStyle(.bordered)
53 | Button(L10n.Content.EditMessage.ConfirmAction.title) { viewStore.send(.confirmTapped) }
54 | .buttonStyle(.borderedProminent)
55 | .disabled(!viewStore.state)
56 | }
57 | }
58 | .controlSize(.large)
59 | }
60 | .padding()
61 | .frame(width: 500)
62 | .frame(minHeight: 200)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Chat/MessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Assets
8 | import ProseUI
9 | import SwiftUI
10 |
11 | public struct MessageView: View {
12 | let model: Message
13 |
14 | public init(model: Message) {
15 | self.model = model
16 | }
17 |
18 | public var body: some View {
19 | HStack(alignment: .top, spacing: 12) {
20 | Avatar(.placeholder, size: 32)
21 |
22 | VStack(alignment: .leading, spacing: 3) {
23 | HStack(alignment: .firstTextBaseline) {
24 | Text(self.model.from.rawValue)
25 | .font(.system(size: 13).bold())
26 | .foregroundColor(Colors.Text.primary.color)
27 |
28 | Text(self.model.timestamp, format: .relative(presentation: .numeric))
29 | .font(.system(size: 11.5))
30 | .foregroundColor(Colors.Text.secondary.color)
31 | }
32 |
33 | Text(self.model.body)
34 | .font(.system(size: 12.5))
35 | .fontWeight(.regular)
36 | .foregroundColor(Colors.Text.primary.color)
37 | }
38 | .textSelection(.enabled)
39 |
40 | Spacer()
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Chat/ReactionPickerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import ProseUI
8 | import SwiftUI
9 |
10 | final class ReactionPickerView: NSHostingView<
11 | ModifiedContent>
12 | > {
13 | typealias Content = ModifiedContent>
14 |
15 | var store: Store {
16 | didSet {
17 | self.rootView = Self.body(store: self.store, action: self.action, dismiss: self.dismiss)
18 | }
19 | }
20 |
21 | let action: CasePath
22 | let dismiss: Action
23 |
24 | init(
25 | store: Store,
26 | action: CasePath,
27 | dismiss: Action
28 | ) {
29 | self.store = store
30 | self.action = action
31 | self.dismiss = dismiss
32 |
33 | super.init(rootView: Self.body(store: store, action: action, dismiss: dismiss))
34 | }
35 |
36 | @MainActor required init(rootView _: Content) {
37 | fatalError("init(rootView:) has not been implemented")
38 | }
39 |
40 | @available(*, unavailable)
41 | required init?(coder _: NSCoder) {
42 | fatalError("init(coder:) has not been implemented")
43 | }
44 |
45 | static func body(
46 | store: Store,
47 | action: CasePath,
48 | dismiss: Action
49 | ) -> Content {
50 | AnyView(Color.clear)
51 | .modifier(ReactionPickerPopup(store: store, action: action, dismiss: dismiss))
52 | }
53 | }
54 |
55 | struct ReactionPickerPopup: ViewModifier {
56 | let store: Store
57 | let action: CasePath
58 | let dismiss: Action
59 |
60 | func body(content: Content) -> some View {
61 | content
62 | .reactionPicker(
63 | store: self.store.scope(state: { $0.pickerState }),
64 | action: self.action.embed(_:),
65 | dismiss: self.dismiss
66 | )
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/ConversationScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import IdentifiedCollections
8 | import PasteboardClient
9 | import SwiftUI
10 | import Toolbox
11 |
12 | public struct ConversationScreen: View {
13 | private let store: StoreOf
14 | private var actions: ViewStore
15 |
16 | public init(store: StoreOf) {
17 | self.store = store
18 | self.actions = ViewStore(self.store.stateless)
19 | }
20 |
21 | public var body: some View {
22 | Chat(store: self.store.scope(state: \.chat, action: ConversationScreenReducer.Action.chat))
23 | .safeAreaInset(edge: .bottom, spacing: 0) {
24 | MessageBar(
25 | store: self.store
26 | .scope(state: \.messageBar, action: ConversationScreenReducer.Action.messageBar)
27 | )
28 | // Make footer have a higher priority, to be accessible over the scroll view
29 | .accessibilitySortPriority(1)
30 | }
31 | .accessibilityIdentifier("ChatWebView")
32 | .accessibilityElement(children: .contain)
33 | .onAppear { self.actions.send(.onAppear) }
34 | .onDisappear { self.actions.send(.onDisappear) }
35 | .safeAreaInset(edge: .trailing, spacing: 0) {
36 | WithViewStore(self.store.scope(state: \.toolbar.childState.isShowingInfo)) { showingInfo in
37 | HStack(spacing: 0) {
38 | ConversationInfoView(
39 | store: self.store.scope(state: \.info, action: ConversationScreenReducer.Action.info)
40 | ).frame(width: 256)
41 | }
42 | .frame(width: showingInfo.state ? 256 : 0, alignment: .leading)
43 | .clipped()
44 | }
45 | }
46 | .toolbar {
47 | Toolbar(
48 | store: self.store
49 | .scope(state: \.toolbar, action: ConversationScreenReducer.Action.toolbar)
50 | )
51 | }
52 | // Hide the navigation title so that our contact's name and availability indicator can act
53 | // as the title instead.
54 | .navigationTitle("")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/InfoSidebar/Views/ActionRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Assets
7 | import SwiftUI
8 |
9 | struct ActionRow: View {
10 | let name: String
11 | var deployTo: Bool = false
12 | let action: () -> Void
13 |
14 | var body: some View {
15 | Button(action: self.action) {
16 | HStack(spacing: 8) {
17 | Text(self.name)
18 | .fontWeight(.medium)
19 |
20 | Spacer()
21 |
22 | if self.deployTo {
23 | Image(systemName: "chevron.right")
24 | .font(.system(size: 10))
25 | .foregroundColor(Colors.Text.primary.color)
26 | }
27 | }
28 | // Make hit box full width
29 | .contentShape([.interaction], Rectangle())
30 | }
31 | .buttonStyle(.plain)
32 | }
33 | }
34 |
35 | struct ActionRow_Previews: PreviewProvider {
36 | static var previews: some View {
37 | ActionRow(
38 | name: "View full profile",
39 | deployTo: true,
40 | action: {}
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/InfoSidebar/Views/EntryRowLabelStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Assets
7 | import SwiftUI
8 |
9 | struct EntryRowLabelStyle: LabelStyle {
10 | static let iconFrameMinWidth: CGFloat = 16
11 |
12 | func makeBody(configuration: Configuration) -> some View {
13 | HStack(alignment: .firstTextBaseline, spacing: 8) {
14 | configuration.icon
15 | .foregroundColor(Colors.State.grey.color)
16 | .frame(width: Self.iconFrameMinWidth, alignment: .center)
17 |
18 | configuration.title
19 | .foregroundColor(Colors.Text.primary.color)
20 | }
21 | .frame(maxWidth: .infinity, alignment: .leading)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/README.md:
--------------------------------------------------------------------------------
1 | Mind you that SwiftUI's `onAppear` hooks will not be called when a different conversation is
2 | selected. SwiftUI is reusing the `ConversationScreen` so we have to call it ourselves. For this
3 | reason you'll see reducers send .onAppear and .onDisappear to their child reducers, e.g. in
4 | `ConversationScreenReducer`…
5 |
6 | ```swift
7 | case .onAppear:
8 | return .merge(
9 | // …
10 | MessageBarReducer().reduce(into: &state.messageBar, action: .onAppear)
11 | .map(Action.messageBar)
12 | )
13 | ```
14 |
15 | `MainScreenReducer` is responsible for initiating this mechanism.
16 |
17 | Let's have a uniform symmetry here and send .onAppear to child reducers last and send .onDisappear
18 | first.
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "conversation")
9 | internal let jsLogger = Logger(subsystem: "org.prose.app", category: "js-ffi")
10 | internal let signposter = OSSignposter(logger: logger)
11 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Toolbar/Toolbar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 | import ProseUI
9 | import SwiftUI
10 |
11 | struct Toolbar: ToolbarContent {
12 | struct ViewState: Equatable {
13 | var contact: Contact
14 | var isShowingInfo: Bool
15 | }
16 |
17 | @ObservedObject var viewStore: ViewStore
18 |
19 | init(store: StoreOf) {
20 | self.viewStore = ViewStore(store.scope(state: ViewState.init))
21 | }
22 |
23 | var body: some ToolbarContent {
24 | ToolbarItemGroup(placement: .navigation) {
25 | CommonToolbarNavigation()
26 |
27 | ContentCommonNameStatusComponent(
28 | name: self.viewStore.contact.name,
29 | status: self.viewStore.contact.availability
30 | )
31 | .padding(.horizontal, 8)
32 | }
33 |
34 | ToolbarItemGroup {
35 | ToolbarSecurity(
36 | jid: self.viewStore.contact.jid,
37 | isVerified: false
38 | )
39 |
40 | ToolbarDivider()
41 |
42 | Button { self.viewStore.send(.startVideoCallTapped) } label: {
43 | Label("Video", systemImage: "video")
44 | }
45 | .disabled(true)
46 |
47 | Toggle(isOn: self.viewStore.binding(
48 | get: \.isShowingInfo,
49 | send: ToolbarReducer.Action.toggleInfoButtonTapped
50 | ).animation()) {
51 | Label("Info", systemImage: "info.circle")
52 | }
53 |
54 | ToolbarDivider()
55 |
56 | CommonToolbarActions()
57 | }
58 | }
59 | }
60 |
61 | extension Toolbar.ViewState {
62 | init(_ state: ToolbarReducer.State) {
63 | self.contact = state.contact
64 | self.isShowingInfo = state.isShowingInfo
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Toolbar/ToolbarReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 |
9 | public struct ToolbarReducer: ReducerProtocol {
10 | public typealias State = ChatSessionState
11 |
12 | public struct ToolbarState: Equatable {
13 | var isShowingInfo = false
14 | }
15 |
16 | public enum Action: Equatable {
17 | case startVideoCallTapped
18 | case toggleInfoButtonTapped
19 | }
20 |
21 | public init() {}
22 |
23 | public var body: some ReducerProtocol {
24 | Reduce { state, action in
25 | switch action {
26 | case .startVideoCallTapped:
27 | logger.info("Start video call tapped")
28 | return .none
29 |
30 | case .toggleInfoButtonTapped:
31 | state.isShowingInfo.toggle()
32 | return .none
33 | }
34 | }
35 | }
36 | }
37 |
38 | extension ToolbarReducer.State {
39 | var contact: Contact {
40 | if let contact = self.userInfos[self.chatId] {
41 | return contact
42 | }
43 | return Contact(
44 | jid: self.chatId,
45 | name: self.chatId.rawValue,
46 | avatar: nil,
47 | availability: .unavailable,
48 | status: nil,
49 | groups: []
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ConversationFeature/Toolbar/ToolbarSecurity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Assets
8 | import SwiftUI
9 |
10 | /// Separated as its own view as we might need to reuse it someday.
11 | struct ToolbarSecurity: View {
12 | let jid: BareJid
13 | let isVerified: Bool
14 |
15 | var body: some View {
16 | HStack(alignment: .firstTextBaseline, spacing: 4) {
17 | Image(systemName: self.isVerified ? "checkmark.seal.fill" : "xmark.seal.fill")
18 | .foregroundColor(self.isVerified ? Colors.State.green.color : .red)
19 | .accessibilityElement()
20 | .accessibilityLabel(self.isVerified ? "Verified" : "Not verified")
21 | .accessibilitySortPriority(1)
22 |
23 | Text(verbatim: self.jid.rawValue)
24 | .foregroundColor(Colors.Text.secondary.color)
25 | .accessibilitySortPriority(2)
26 | }
27 | .padding(.horizontal, 8)
28 | .accessibilityElement(children: .combine)
29 | }
30 | }
31 |
32 | #if DEBUG
33 | struct ToolbarSecurity_Previews: PreviewProvider {
34 | static var previews: some View {
35 | VStack(alignment: .leading) {
36 | ToolbarSecurity(
37 | jid: "valerian@prose.org",
38 | isVerified: true
39 | )
40 | ToolbarSecurity(
41 | jid: "valerian@prose.org",
42 | isVerified: false
43 | )
44 | ToolbarSecurity(
45 | jid: "valerian@prose.org",
46 | isVerified: false
47 | )
48 | .redacted(reason: .placeholder)
49 | .previewDisplayName("Placeholder")
50 | }
51 | .previewLayout(.sizeThatFits)
52 | }
53 | }
54 | #endif
55 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/CredentialsClient/CredentialsClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 |
9 | public struct CredentialsClient {
10 | public var loadCredentials: (_ jid: BareJid) throws -> Credentials?
11 | public var save: (_ credentials: Credentials) throws -> Void
12 | public var deleteCredentials: (_ jid: BareJid) throws -> Void
13 | }
14 |
15 | public extension DependencyValues {
16 | var credentialsClient: CredentialsClient {
17 | get { self[CredentialsClient.self] }
18 | set { self[CredentialsClient.self] = newValue }
19 | }
20 | }
21 |
22 | extension CredentialsClient: TestDependencyKey {
23 | public static var testValue = CredentialsClient(
24 | loadCredentials: unimplemented("\(Self.self).loadCredentials"),
25 | save: unimplemented("\(Self.self).save"),
26 | deleteCredentials: unimplemented("\(Self.self).deleteCredentials")
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/CredentialsClient/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "credentials-client")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/AuthenticationReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 |
9 | public struct AuthenticationReducer: ReducerProtocol {
10 | public struct State: Equatable {
11 | var recoveryEmail = "baptiste@jamin.me"
12 | var recoveryPhone = "+33631893345"
13 | var isMfaEnabled = false
14 |
15 | var mfaStateLabel: String {
16 | self.isMfaEnabled
17 | ? L10n.EditProfile.Authentication.MfaStatus.StateEnabled.label
18 | : L10n.EditProfile.Authentication.MfaStatus.StateDisabled.label
19 | }
20 |
21 | public init() {}
22 | }
23 |
24 | public enum Action: Equatable, BindableAction {
25 | case changePasswordTapped
26 | case editRecoveryEmailTapped
27 | case disableMFATapped
28 | case editRecoveryPhoneTapped
29 | case binding(BindingAction)
30 | }
31 |
32 | public init() {}
33 |
34 | public var body: some ReducerProtocol {
35 | BindingReducer()
36 | self.core
37 | }
38 |
39 | @ReducerBuilder
40 | private var core: some ReducerProtocol {
41 | Reduce { state, action in
42 | switch action {
43 | case .changePasswordTapped:
44 | logger.trace("Change password tapped")
45 | return .none
46 |
47 | case .editRecoveryEmailTapped:
48 | logger.trace("Edit recovery email tapped")
49 | return .none
50 |
51 | case .disableMFATapped:
52 | state.isMfaEnabled.toggle()
53 | return .none
54 |
55 | case .editRecoveryPhoneTapped:
56 | logger.trace("Edit recovery phone tapped")
57 | return .none
58 |
59 | case .binding:
60 | return .none
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Components/ContentSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import SwiftUI
8 |
9 | struct ContentSection: View {
10 | let header: String
11 | let footer: String
12 | let content: () -> Content
13 |
14 | var body: some View {
15 | VStack(alignment: .leading, spacing: 16) {
16 | Text(verbatim: self.header)
17 | .font(.title3.bold())
18 | .foregroundColor(.secondary)
19 | .multilineTextAlignment(.leading)
20 | .padding(.horizontal)
21 | // Ignore as it's already the container label
22 | .accessibilityHidden(true)
23 | self.content()
24 | .layoutPriority(1)
25 | Text(self.footer.asMarkdown)
26 | .font(.footnote)
27 | .foregroundColor(.secondary)
28 | .multilineTextAlignment(.leading)
29 | .padding(.horizontal)
30 | .fixedSize(horizontal: false, vertical: true)
31 | }
32 | .accessibilityElement(children: .contain)
33 | .accessibilityLabel(self.header)
34 | }
35 | }
36 |
37 | struct ContentSection_Previews: PreviewProvider {
38 | static var previews: some View {
39 | ScrollView(.vertical) {
40 | ContentSection(
41 | header: "Current location",
42 | footer: """
43 | You can opt-in to automatic location updates based on your last used device location. It is handy if you travel a lot, and would like this to be auto-managed. Your current city and country will be shared, not your exact GPS location.
44 |
45 | **Note that geolocation permissions are required for automatic mode.**
46 | """
47 | ) {
48 | Color.red
49 | .frame(height: 256)
50 | }
51 | }
52 | .frame(height: 480)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Components/SecondaryRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct SecondaryRow: View {
9 | let label: String
10 | let content: () -> Content
11 |
12 | init(_ label: String, @ViewBuilder content: @escaping () -> Content) {
13 | self.label = label
14 | self.content = content
15 | }
16 |
17 | var body: some View {
18 | HStack {
19 | Text(verbatim: self.label)
20 | .font(.headline.weight(.medium))
21 | // Ignore as it's already the container label
22 | .accessibilityHidden(true)
23 | self.content()
24 | }
25 | .accessibilityElement(children: .contain)
26 | .accessibilityLabel(self.label)
27 | }
28 | }
29 |
30 | struct SecondaryRow_Previews: PreviewProvider {
31 | static var previews: some View {
32 | VStack(alignment: .leading) {
33 | SecondaryRow("Status:") {
34 | HStack(spacing: 4) {
35 | Image(systemName: "location.fill")
36 | .foregroundColor(.blue)
37 | Text(verbatim: "Automatic")
38 | }
39 | }
40 | SecondaryRow("Geolocation permission:") {
41 | Text(verbatim: "Allowed")
42 | Button("Manage") {}
43 | .controlSize(.small)
44 | }
45 | }
46 | .padding()
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/IdentityReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 |
8 | public struct IdentityReducer: ReducerProtocol {
9 | public struct State: Equatable {
10 | @BindingState var firstName: String
11 | @BindingState var lastName: String
12 | @BindingState var email: String
13 | @BindingState var phone: String
14 |
15 | @BindingState var isNameVerified: Bool
16 | @BindingState var isEmailVerified: Bool
17 | @BindingState var isPhoneVerified: Bool
18 |
19 | public init(
20 | firstName: String = "Baptiste",
21 | lastName: String = "Jamin",
22 | email: String = "baptiste@crisp.chat",
23 | phone: String = "+33631893345",
24 | isNameVerified: Bool = false,
25 | isEmailVerified: Bool = true,
26 | isPhoneVerified: Bool = false
27 | ) {
28 | self.firstName = firstName
29 | self.lastName = lastName
30 | self.email = email
31 | self.phone = phone
32 | self.isNameVerified = isNameVerified
33 | self.isEmailVerified = isEmailVerified
34 | self.isPhoneVerified = isPhoneVerified
35 | }
36 | }
37 |
38 | public enum Action: Equatable, BindableAction {
39 | case verifyNameTapped
40 | case verifyEmailTapped
41 | case verifyPhoneTapped
42 | case binding(BindingAction)
43 | }
44 |
45 | public var body: some ReducerProtocol {
46 | BindingReducer()
47 | self.core
48 | }
49 |
50 | @ReducerBuilder
51 | private var core: some ReducerProtocol {
52 | Reduce { state, action in
53 | switch action {
54 | case .verifyNameTapped:
55 | state.isNameVerified = true
56 | return .none
57 |
58 | case .verifyEmailTapped:
59 | state.isEmailVerified = true
60 | return .none
61 |
62 | case .verifyPhoneTapped:
63 | state.isPhoneVerified = true
64 | return .none
65 |
66 | case .binding:
67 | return .none
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/ProfileReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 |
9 | public struct ProfileReducer: ReducerProtocol {
10 | public struct State: Equatable {
11 | @BindingState var organization: String
12 | @BindingState var jobTitle: String
13 | @BindingState var autoDetectLocation: Bool
14 | @BindingState var location: String
15 | @BindingState var isLocationPermissionAllowed: Bool
16 |
17 | var locationPermissionLabel: String {
18 | self.isLocationPermissionAllowed
19 | ? L10n.EditProfile.Profile.LocationPermission.StateAllowed.label
20 | : L10n.EditProfile.Profile.LocationPermission.StateDenied.label
21 | }
22 |
23 | public init(
24 | organization: String = "Crisp",
25 | jobTitle: String = "CEO",
26 | autoDetectLocation: Bool = true,
27 | location: String = "Nantes, France",
28 | isLocationPermissionAllowed: Bool = true
29 | ) {
30 | self.organization = organization
31 | self.jobTitle = jobTitle
32 | self.autoDetectLocation = autoDetectLocation
33 | self.location = location
34 | self.isLocationPermissionAllowed = isLocationPermissionAllowed
35 | }
36 | }
37 |
38 | public enum Action: Equatable, BindableAction {
39 | case manageLocationPermissionTapped
40 | case binding(BindingAction)
41 | }
42 |
43 | public init() {}
44 |
45 | public var body: some ReducerProtocol {
46 | BindingReducer()
47 | self.core
48 | }
49 |
50 | @ReducerBuilder
51 | private var core: some ReducerProtocol {
52 | Reduce { state, action in
53 | switch action {
54 | case .manageLocationPermissionTapped:
55 | state.isLocationPermissionAllowed.toggle()
56 | return .none
57 |
58 | case .binding:
59 | return .none
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Sidebar/Sidebar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 | import IdentifiedCollections
9 | import SwiftUI
10 |
11 | struct Sidebar: View {
12 | static let minWidth: CGFloat = 228
13 |
14 | let store: StoreOf
15 |
16 | var body: some View {
17 | WithViewStore(self.store, removeDuplicates: { $0.selection == $1.selection }) { viewStore in
18 | List(selection: viewStore.binding(\.$selection)) {
19 | ForEachStore(
20 | self.store.scope(state: \.rows, action: SidebarReducer.Action.row),
21 | content: SidebarRow.init(store:)
22 | )
23 | }
24 | .listStyle(.sidebar)
25 | .frame(minWidth: Self.minWidth)
26 | }
27 | .safeAreaInset(edge: .top, spacing: 0) {
28 | SidebarHeader(
29 | store: self.store.scope(state: \.header, action: SidebarReducer.Action.header)
30 | )
31 | .padding([.horizontal, .top], 24)
32 | .padding(.bottom, 8)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Sidebar/SidebarRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import ComposableArchitecture
8 | import SwiftUI
9 |
10 | struct SidebarRow: View {
11 | let store: StoreOf
12 |
13 | var body: some View {
14 | WithViewStore(self.store) { viewStore in
15 | Self.content(viewStore: viewStore)
16 | }
17 | }
18 |
19 | @ViewBuilder
20 | static func content(viewStore: ViewStoreOf) -> some View {
21 | let foregroundColor = viewStore.foregroundColor
22 | HStack {
23 | ZStack {
24 | Circle()
25 | .fill(viewStore.isSelected ? Color.white : Color.accentColor)
26 | Image(systemName: viewStore.icon)
27 | }
28 | .symbolVariant(.fill)
29 | .foregroundColor(viewStore.isSelected ? Color.accentColor : Color.white)
30 | .frame(width: 24, height: 24)
31 | .accessibilityHidden(true)
32 | VStack(alignment: .leading, spacing: 0) {
33 | Text(verbatim: viewStore.headline)
34 | Text(verbatim: viewStore.subheadline)
35 | .font(.subheadline)
36 | .foregroundColor(foregroundColor.opacity(0.75))
37 | }
38 | .frame(maxWidth: .infinity, alignment: .leading)
39 | .accessibilityElement(children: .ignore)
40 | .accessibilityLabel(
41 | L10n.EditProfile.Sidebar.Row
42 | .axLabel(viewStore.headline, viewStore.subheadline)
43 | )
44 | Image(systemName: "chevron.forward.circle.fill")
45 | .symbolVariant(.fill)
46 | .foregroundColor(viewStore.isSelected ? .white : .primary)
47 | .opacity(viewStore.isSelected ? 1 : 0)
48 | .accessibilityHidden(true)
49 | }
50 | .font(.headline)
51 | .padding(.vertical, 4)
52 | .padding(.horizontal, 8)
53 | .foregroundColor(foregroundColor)
54 | .tag(viewStore.id)
55 | .accessibilityElement(children: .combine)
56 | .accessibilityAddTraits(viewStore.isSelected ? .isSelected : [])
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Sidebar/SidebarRowReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import SwiftUI
8 |
9 | public struct SidebarRowReducer: ReducerProtocol {
10 | public struct State: Equatable, Identifiable {
11 | public let id: SidebarReducer.Selection
12 |
13 | let icon: String
14 | let headline: String
15 | let subheadline: String
16 | var isSelected = false
17 |
18 | var foregroundColor: Color { self.isSelected ? .white : .primary }
19 | }
20 |
21 | public enum Action: Equatable, BindableAction {
22 | case binding(BindingAction)
23 | }
24 |
25 | public init() {}
26 |
27 | public var body: some ReducerProtocol {
28 | BindingReducer()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/EditProfileFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "edit-profile")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/MainScreenFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "main-window")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Mocks/BareJid.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 |
8 | public extension BareJid {
9 | static let janeDoe: BareJid = "jane.doe@.prose.org"
10 | static let johnDoe: BareJid = "john.doe@.prose.org"
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Mocks/Contact.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Foundation
8 |
9 | public extension Contact {
10 | static func mock(
11 | jid: BareJid = "user@prose.org",
12 | name: String = "Prose User",
13 | avatar: URL? = nil,
14 | availability: Availability = .unavailable,
15 | status: String? = nil,
16 | groups: [String] = []
17 | ) -> Self {
18 | .init(
19 | jid: jid,
20 | name: name,
21 | avatar: avatar,
22 | availability: availability,
23 | status: status,
24 | groups: groups
25 | )
26 | }
27 | }
28 |
29 | public extension Contact {
30 | static let johnDoe = Contact.mock(
31 | jid: "john.doe@prose.org",
32 | name: "John Doe"
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Mocks/Message.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Foundation
8 |
9 | public extension Message {
10 | static func mock(
11 | id: MessageId = .init(UUID().uuidString),
12 | from: BareJid = "jane.doe@prose.org",
13 | body: String = "Hello World!",
14 | timestamp: Date = Date(),
15 | isRead: Bool = false,
16 | isEdited: Bool = false,
17 | isDelivered: Bool = false,
18 | reactions: [Reaction] = []
19 | ) -> Self {
20 | .init(
21 | id: id,
22 | stanzaId: "stanza-id",
23 | from: from,
24 | body: body,
25 | timestamp: timestamp,
26 | isRead: isRead,
27 | isEdited: isEdited,
28 | isDelivered: isDelivered,
29 | reactions: reactions
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Mocks/RandomUser/RandomUser+JSON.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public extension RNDResult {
9 | static func all() -> [RNDResult] {
10 | let data = try! Data(
11 | contentsOf: Foundation.Bundle.module
12 | .url(forResource: "random_user", withExtension: "json")!
13 | )
14 | return try! JSONDecoder().decode(RNDRandomUserResponse.self, from: data).results
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Mocks/SessionState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 |
8 | public extension SessionState {
9 | static func mock(
10 | account: Account = .init(
11 | jid: .janeDoe,
12 | status: .connected,
13 | settings: .init(availability: .available)
14 | ),
15 | _ childState: ChildState
16 | ) -> Self {
17 | .init(
18 | selectedAccountId: account.jid,
19 | accounts: [account],
20 | childState: childState
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/NotificationsClient/NotificationsClient+Noop.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Combine
7 | import ComposableArchitecture
8 | import Foundation
9 |
10 | #if DEBUG
11 | public extension NotificationsClient {
12 | static var noop = NotificationsClient(
13 | promptForPushNotifications: {},
14 | notificationPermission: { Empty(completeImmediately: false).eraseToEffect() },
15 | scheduleLocalNotification: { _, _ in }
16 | )
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/NotificationsClient/NotificationsClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 | import Foundation
9 |
10 | public enum NotificationPermission: Equatable {
11 | case notDetermined
12 | case denied
13 | case authorized
14 | case provisional
15 | case ephemeral
16 | }
17 |
18 | public struct NotificationsClient {
19 | public var promptForPushNotifications: () -> Void
20 | public var notificationPermission: () -> EffectTask
21 | public var scheduleLocalNotification: (Message, UserInfo) async throws -> Void
22 | }
23 |
24 | public extension DependencyValues {
25 | var notificationsClient: NotificationsClient {
26 | get { self[NotificationsClient.self] }
27 | set { self[NotificationsClient.self] = newValue }
28 | }
29 | }
30 |
31 | extension NotificationsClient: TestDependencyKey {
32 | public static var testValue = NotificationsClient(
33 | promptForPushNotifications: unimplemented("\(Self.self).promptForPushNotifications"),
34 | notificationPermission: unimplemented("\(Self.self).notificationPermission"),
35 | scheduleLocalNotification: unimplemented("\(Self.self).scheduleLocalNotification")
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/NotificationsClient/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "notifications")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PasteboardClient/PasteboardClient+Live.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | #if os(macOS)
7 | import Cocoa
8 | #elseif canImport(UIKit)
9 | import UIKit
10 | #endif
11 | import ComposableArchitecture
12 |
13 | extension PasteboardClient: DependencyKey {
14 | public static var liveValue = PasteboardClient.live()
15 | }
16 |
17 | public extension PasteboardClient {
18 | #if os(macOS)
19 | static func live(
20 | pasteboard: NSPasteboard = .general
21 | ) -> Self {
22 | .init(
23 | copyString: {
24 | pasteboard.clearContents()
25 | pasteboard.setString($0, forType: .string)
26 | }
27 | )
28 | }
29 |
30 | #elseif canImport(UIKit)
31 | static func live(
32 | pasteboard: UIPasteboard = .general
33 | ) -> Self {
34 | .init(
35 | copyString: { pasteboard.string = $0 }
36 | )
37 | }
38 | #endif
39 | }
40 |
41 | #if DEBUG
42 | public extension PasteboardClient {
43 | static var noop = PasteboardClient(
44 | copyString: { _ in () }
45 | )
46 | }
47 | #endif
48 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PasteboardClient/PasteboardClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import Foundation
8 |
9 | public struct PasteboardClient {
10 | public var copyString: (String) -> Void
11 |
12 | public init(copyString: @escaping (String) -> Void) {
13 | self.copyString = copyString
14 | }
15 | }
16 |
17 | public extension DependencyValues {
18 | var pasteboardClient: PasteboardClient {
19 | get { self[PasteboardClient.self] }
20 | set { self[PasteboardClient.self] = newValue }
21 | }
22 | }
23 |
24 | extension PasteboardClient: TestDependencyKey {
25 | public static var testValue = PasteboardClient(
26 | copyString: unimplemented("\(Self.self).copyString")
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Generated/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Generated/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/ImageAsset+URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public extension ImageAsset {
9 | var customURL: URL? {
10 | var components = URLComponents()
11 | components.scheme = "asset"
12 | components.path = Bundle.fixedModule.bundlePath
13 | components.queryItems = [
14 | .init(name: "imageName", value: self.name),
15 | ]
16 | return components.url
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/alexandre.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatar.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/alexandre.imageset/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/alexandre.imageset/avatar.png
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/antoine.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatar.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/antoine.imageset/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/antoine.imageset/avatar.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/baptiste.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatar.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/baptiste.imageset/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/baptiste.imageset/avatar.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/camille.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatag.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/camille.imageset/avatag.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/camille.imageset/avatag.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/constellation-health.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "constellation-health.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/constellation-health.imageset/constellation-health.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/constellation-health.imageset/constellation-health.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/eliott.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatar.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/eliott.imageset/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/eliott.imageset/avatar.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/julien.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "julien.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/julien.imageset/julien.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/julien.imageset/julien.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/valerian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "avatar.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "original"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/valerian.imageset/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/avatars/valerian.imageset/avatar.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-crisp.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "logo-crisp.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-crisp.imageset/logo-crisp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-crisp.imageset/logo-crisp.png
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-makair.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "logo-makair.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-makair.imageset/logo-makair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/logo-makair.imageset/logo-makair.png
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/webcam-valerian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "webcam.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/webcam-valerian.imageset/webcam.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/PreviewAssets/Resources/Assets.xcassets/webcam-valerian.imageset/webcam.jpg
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/PreviewAssets/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "preview-assets")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCore/AccountBookmarksClient+Live.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ComposableArchitecture
7 | import Foundation
8 | import ProseCoreFFI
9 |
10 | extension AccountBookmarksClient {
11 | static func live(
12 | bookmarksURL: URL = URL.documentsDirectory
13 | .appending(component: "accounts.json")
14 | ) -> Self {
15 | let client = ProseCoreFFI.AccountBookmarksClient(bookmarksPath: bookmarksURL)
16 |
17 | return .init(
18 | loadBookmarks: {
19 | try client.loadBookmarks()
20 | },
21 | addBookmark: { jid in
22 | try await Task {
23 | try client.addBookmark(jid: jid, selectBookmark: true)
24 | }.result.get()
25 | },
26 | removeBookmark: { jid in
27 | try await Task {
28 | try client.removeBookmark(jid: jid)
29 | }.result.get()
30 | },
31 | selectBookmark: { jid in
32 | try await Task {
33 | try client.selectBookmark(jid: jid)
34 | }.result.get()
35 | }
36 | )
37 | }
38 | }
39 |
40 | extension AccountBookmarksClient: DependencyKey {
41 | public static var liveValue: AccountBookmarksClient = .live()
42 | }
43 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCore/AccountBookmarksClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 | import Foundation
9 |
10 | public struct AccountBookmarksClient {
11 | public var loadBookmarks: () throws -> [AccountBookmark]
12 | public var addBookmark: (BareJid) async throws -> Void
13 | public var removeBookmark: (BareJid) async throws -> Void
14 | public var selectBookmark: (BareJid) async throws -> Void
15 | }
16 |
17 | public extension DependencyValues {
18 | var accountBookmarksClient: AccountBookmarksClient {
19 | get { self[AccountBookmarksClient.self] }
20 | set { self[AccountBookmarksClient.self] = newValue }
21 | }
22 | }
23 |
24 | extension AccountBookmarksClient: TestDependencyKey {
25 | public static var testValue = AccountBookmarksClient(
26 | loadBookmarks: unimplemented("\(Self.self).loadBookmarks"),
27 | addBookmark: unimplemented("\(Self.self).saveBookmark"),
28 | removeBookmark: unimplemented("\(Self.self).removeBookmark"),
29 | selectBookmark: unimplemented("\(Self.self).selectBookmark")
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCore/Exports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | @_exported import AppDomain
7 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/API/MessagingContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Foundation
8 |
9 | public struct MessagingContext {
10 | public let setAccountJID: JSFunc1
11 | public let setStyleTheme: JSFunc1
12 |
13 | public init(evaluator: @escaping JSEvaluator) {
14 | let cls = JSClass(name: "MessagingContext", evaluator: evaluator)
15 | self.setAccountJID = cls.setAccountJID
16 | self.setStyleTheme = cls.setStyleTheme
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/API/MessagingStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Foundation
8 | import IdentifiedCollections
9 |
10 | public struct MessagingStore {
11 | private let signpostID = signposter.makeSignpostID()
12 |
13 | public let insertMessages: JSRestFunc1<[Message], Void>
14 | public let retractMessage: JSFunc1
15 | public let highlightMessage: JSFunc1
16 | public let interact: JSFunc3
17 | public let identify: JSFunc2
18 |
19 | private let update: JSFunc2
20 |
21 | init(evaluator: @escaping JSEvaluator) {
22 | let cls = JSClass(name: "MessagingStore", evaluator: evaluator)
23 | self.insertMessages = cls.insert
24 | self.update = cls.update
25 | self.retractMessage = cls.retract
26 | self.highlightMessage = cls.highlight
27 | self.identify = cls.identify
28 | self.interact = cls.interact
29 | }
30 |
31 | public func updateMessages(
32 | to messages: [Message],
33 | oldMessages: inout IdentifiedArrayOf
34 | ) {
35 | let interval = signposter.beginInterval(#function, id: self.signpostID)
36 |
37 | let messages = IdentifiedArrayOf(uniqueElements: messages)
38 | defer { oldMessages = messages }
39 |
40 | let diff = messages.difference(from: oldMessages)
41 |
42 | for messageId in diff.removedIds {
43 | self.retractMessage(messageId)
44 | }
45 | for messageId in diff.updatedIds {
46 | if let message = messages[id: messageId] {
47 | self.updateMessage(message)
48 | }
49 | }
50 | self.insertMessages(diff.insertedIds.compactMap { messages[id: $0] })
51 |
52 | signposter.endInterval(#function, interval)
53 | }
54 |
55 | public func updateMessage(_ message: Message) {
56 | self.update(message.id, message)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/FFI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public struct FFI {
9 | public let messagingContext: MessagingContext
10 | public let messagingStore: MessagingStore
11 |
12 | public init(evaluator: @escaping JSEvaluator) {
13 | self.messagingContext = MessagingContext(evaluator: evaluator)
14 | self.messagingStore = MessagingStore(evaluator: evaluator)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Generated/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/ProseCoreViews/Generated/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Generated/Files+Generated.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | // swiftlint:disable all
7 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
8 |
9 | import Foundation
10 |
11 | // swiftlint:disable superfluous_disable_command file_length line_length implicit_return
12 |
13 | // MARK: - Files
14 |
15 | // swiftlint:disable explicit_type_interface identifier_name
16 | // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
17 | public enum Files {
18 | /// messaging.html
19 | public static let messagingHtml = File(
20 | name: "messaging",
21 | ext: "html",
22 | relativePath: "",
23 | mimeType: "text/html"
24 | )
25 | }
26 |
27 | // swiftlint:enable explicit_type_interface identifier_name
28 | // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
29 |
30 | // MARK: - Implementation Details
31 |
32 | public struct File {
33 | public let name: String
34 | public let ext: String?
35 | public let relativePath: String
36 | public let mimeType: String
37 |
38 | public var url: URL {
39 | url(locale: nil)
40 | }
41 |
42 | public func url(locale: Locale?) -> URL {
43 | let bundle = Bundle.fixedModule
44 | let url = bundle.url(
45 | forResource: self.name,
46 | withExtension: self.ext,
47 | subdirectory: self.relativePath,
48 | localization: locale?.identifier
49 | )
50 | guard let result = url else {
51 | let file = self.name + (self.ext.flatMap { ".\($0)" } ?? "")
52 | fatalError("Could not locate file named \(file)")
53 | }
54 | return result
55 | }
56 |
57 | public var path: String {
58 | path(locale: nil)
59 | }
60 |
61 | public func path(locale: Locale?) -> String {
62 | self.url(locale: locale).path
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Helpers/IdentifiedArray+Difference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import struct IdentifiedCollections.IdentifiedArray
7 | import struct OrderedCollections.OrderedSet
8 |
9 | extension IdentifiedArray where Element: Equatable {
10 | struct Difference: Equatable {
11 | let removedIds, insertedIds: OrderedSet
12 | let updatedIds: Set
13 | }
14 |
15 | func difference(from other: Self) -> Difference {
16 | let ids: OrderedSet = self.ids
17 | let otherIds: OrderedSet = other.ids
18 |
19 | return Difference(
20 | removedIds: otherIds.subtracting(ids),
21 | insertedIds: ids.subtracting(otherIds),
22 | updatedIds: Set(ids.intersection(otherIds)).filter { self[id: $0] != other[id: $0] }
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Helpers/JSEventError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public enum JSEventError: Error, Equatable {
9 | case badSerialization, decodingError(String)
10 | }
11 |
12 | extension JSEventError: CustomDebugStringConvertible {
13 | public var debugDescription: String {
14 | switch self {
15 | case .badSerialization:
16 | return "JS message body should be serialized as a String"
17 | case let .decodingError(debugDescription):
18 | return debugDescription
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Helpers/NSError+JavaScript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public extension NSError {
9 | var crisp_javaScriptExceptionMessage: String {
10 | (self.userInfo["WKJavaScriptExceptionMessage"] as? String) ?? self.localizedDescription
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/ProseCoreViews/Resources/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prose-im/prose-app-macos/54c76806ff2f24d35cfdd2598cd8f2816c0a5655/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/.gitkeep
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/action-more.77dcdfab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/action-reactions.7a469ec6.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/file-other-option-get.a1a4420f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Resources/Views/origin-attribute-insecure.aa6021b6.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "views")
9 | internal let signposter = OSSignposter(logger: logger)
10 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Types/ColorScheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public enum StyleTheme: String, Encodable {
9 | case light
10 | case dark
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Types/EventOrigin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Point: Equatable, Decodable {
9 | public let x, y: Double
10 | public var cgPoint: CGPoint { CGPoint(x: self.x, y: self.y) }
11 | }
12 |
13 | public struct Frame: Equatable, Decodable {
14 | public let x, y, width, height: Double
15 | public var cgRect: CGRect { CGRect(x: self.x, y: self.y, width: self.width, height: self.height) }
16 | }
17 |
18 | public struct EventOrigin: Equatable, Decodable {
19 | public let anchor: Point
20 | public let parent: Frame?
21 | public var cgRect: CGRect {
22 | self.parent?.cgRect ?? CGRect(origin: self.anchor.cgPoint, size: .zero)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Types/MessageAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public enum MessageAction: String, Encodable {
9 | case reactions, actions
10 | }
11 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseCoreViews/Types/MessageEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 |
8 | public enum MessageEvent: Equatable {
9 | case showMenu(MessageMenuHandlerPayload)
10 | case toggleReaction(ToggleReactionHandlerPayload)
11 | case showReactions(ShowReactionsHandlerPayload)
12 | case reachedEndOfList(ReachedEndOfListPayload)
13 | }
14 |
15 | public struct MessageMenuHandlerPayload: Equatable, Decodable {
16 | public let id: Message.ID?
17 | public let origin: EventOrigin
18 | }
19 |
20 | public struct ShowReactionsHandlerPayload: Equatable, Decodable {
21 | public let id: Message.ID?
22 | public let origin: EventOrigin
23 | }
24 |
25 | public struct ToggleReactionHandlerPayload: Equatable, Decodable {
26 | public let id: Message.ID?
27 | public let reaction: Emoji
28 | }
29 |
30 | public struct ReachedEndOfListPayload: Equatable, Decodable {
31 | public enum Direction: String, Decodable {
32 | case forwards
33 | case backwards
34 | }
35 |
36 | public let direction: Direction
37 | }
38 |
39 | public extension MessageEvent {
40 | enum Kind: String {
41 | case showMenu = "message:actions:view"
42 | case toggleReaction = "message:reactions:react"
43 | case showReactions = "message:reactions:view"
44 | case reachedEndOfList = "message:history:seek"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/AvailabilityIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Assets
8 | import SwiftUI
9 |
10 | public struct AvailabilityIndicator: View {
11 | @Environment(\.redactionReasons) private var redactionReasons
12 |
13 | private let availability: Availability
14 | private let size: CGFloat
15 |
16 | public init(
17 | availability: Availability,
18 | size: CGFloat = 11.0
19 | ) {
20 | self.availability = availability
21 | self.size = size
22 | }
23 |
24 | public init(_ availability: Availability) {
25 | self.init(availability: availability)
26 | }
27 |
28 | public var body: some View {
29 | // Having a `ZStack` with the background circle always present allows animations.
30 | // Conditional views (aka `if`, `switch`…) break identity, and thus animations.
31 | ZStack {
32 | Circle()
33 | .fill(Color.white)
34 | Circle()
35 | .fill(self.redactionReasons.contains(.placeholder) ? .gray : self.availability.fillColor)
36 | .padding(2)
37 | Circle()
38 | .strokeBorder(Color(nsColor: .separatorColor), lineWidth: 0.5)
39 | }
40 | .frame(width: self.size, height: self.size)
41 | .drawingGroup()
42 | .accessibilityElement(children: .ignore)
43 | .accessibilityLabel(String(describing: self.availability))
44 | }
45 | }
46 |
47 | private extension Availability {
48 | var fillColor: Color {
49 | switch self {
50 | case .available:
51 | return .green
52 | case .doNotDisturb, .away, .unavailable:
53 | return .orange
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Common/ColoredIconLabelStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct ColoredIconLabelStyle: LabelStyle {
9 | public func makeBody(configuration: Configuration) -> some View {
10 | Label {
11 | configuration.title
12 | } icon: {
13 | configuration.icon
14 | .foregroundColor(.accentColor)
15 | }
16 | }
17 | }
18 |
19 | public extension LabelStyle where Self == ColoredIconLabelStyle {
20 | static var coloredIcon: Self { ColoredIconLabelStyle() }
21 | }
22 |
23 | struct ColoredIconLabelStyle_Previews: PreviewProvider {
24 | static var previews: some View {
25 | Label("Valerian Saliou", systemImage: "message")
26 | .labelStyle(.coloredIcon)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Common/ContentCommonNameStatusComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import SwiftUI
8 |
9 | public struct ContentCommonNameStatusComponent: View {
10 | var name: String
11 | var status: Availability = .unavailable
12 |
13 | public init(name: String, status: Availability = .unavailable) {
14 | self.name = name
15 | self.status = status
16 | }
17 |
18 | public var body: some View {
19 | HStack {
20 | OnlineStatusIndicator(self.status)
21 | .offset(x: 3, y: 1)
22 | .accessibilitySortPriority(1)
23 |
24 | Text(verbatim: self.name)
25 | .font(.system(size: 14).bold())
26 | .accessibilitySortPriority(2)
27 | }
28 | .accessibilityElement(children: .combine)
29 | }
30 | }
31 |
32 | struct ContentCommonNameStatusComponent_Previews: PreviewProvider {
33 | static var previews: some View {
34 | ContentCommonNameStatusComponent(
35 | name: "Valerian Saliou",
36 | status: .available
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Common/ShadowedButtonStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct ShadowedButtonStyle: ButtonStyle {
9 | public func makeBody(configuration: Configuration) -> some View {
10 | configuration.label
11 | .opacity(configuration.isPressed ? 0.5 : 1)
12 | .padding(.vertical, 6)
13 | .padding(.horizontal, 12)
14 | .background {
15 | RoundedRectangle(cornerRadius: 6)
16 | .fill(.background)
17 | .shadow(color: .gray.opacity(0.5), radius: 2)
18 | }
19 | }
20 | }
21 |
22 | public extension ButtonStyle where Self == ShadowedButtonStyle {
23 | static var shadowed: Self { ShadowedButtonStyle() }
24 | }
25 |
26 | struct ShadowedButtonStyle_Previews: PreviewProvider {
27 | static var previews: some View {
28 | VStack {
29 | Button(action: {}) {
30 | Label("Reply", systemImage: "arrowshape.turn.up.right")
31 | .frame(maxWidth: .infinity)
32 | }
33 | .foregroundColor(.accentColor)
34 | Button(action: {}) {
35 | Text("Mark read")
36 | .frame(maxWidth: .infinity)
37 | }
38 | }
39 | .frame(width: 96)
40 | .labelStyle(.vertical)
41 | .buttonStyle(.shadowed)
42 | .padding()
43 | .background(.background)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Common/VerticalLabelStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct VerticalLabelStyle: LabelStyle {
9 | public func makeBody(configuration: Configuration) -> some View {
10 | VStack {
11 | configuration.icon
12 | .font(.system(size: 24))
13 | configuration.title
14 | }
15 | }
16 | }
17 |
18 | public extension LabelStyle where Self == VerticalLabelStyle {
19 | static var vertical: Self { VerticalLabelStyle() }
20 | }
21 |
22 | struct VerticalLabelStyle_Previews: PreviewProvider {
23 | static var previews: some View {
24 | Label("Reply", systemImage: "arrowshape.turn.up.right")
25 | .labelStyle(.vertical)
26 | Button(action: {}) {
27 | Label("Reply", systemImage: "arrowshape.turn.up.right")
28 | }
29 | // .buttonStyle(.plain)
30 | .labelStyle(.vertical)
31 | .padding()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/HelpButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | // MARK: - View
9 |
10 | /// A pre-styled button that presents a popover with the provided content when tapped.
11 | public struct HelpButton: View {
12 | @Environment(\.redactionReasons) private var redactionReasons
13 |
14 | @State private var isShowingPopover: Bool = false
15 |
16 | let content: () -> Content
17 |
18 | public init(content: @escaping () -> Content) {
19 | self.content = content
20 | }
21 |
22 | public var body: some View {
23 | Button { self.isShowingPopover = true } label: {
24 | Image(systemName: "questionmark")
25 | }
26 | .buttonStyle(CircleButtonStyle())
27 | .unredacted()
28 | .disabled(self.redactionReasons.contains(.placeholder))
29 | .popover(isPresented: self.$isShowingPopover, content: self.content)
30 | }
31 | }
32 |
33 | private struct CircleButtonStyle: ButtonStyle {
34 | @Environment(\.isEnabled) private var isEnabled
35 | func makeBody(configuration: Self.Configuration) -> some View {
36 | configuration.label
37 | .font(.system(size: 12, weight: .semibold, design: .rounded))
38 | .frame(width: 20, height: 20)
39 | .contentShape(Circle())
40 | .background {
41 | Circle()
42 | .fill(Color(nsColor: .controlColor))
43 | .shadow(color: Color.black.opacity(0.25), radius: 0, y: 0.5)
44 | }
45 | .overlay {
46 | Circle()
47 | .strokeBorder(Color.black.opacity(0.15), lineWidth: 0.5)
48 | }
49 | .opacity((configuration.isPressed || !self.isEnabled) ? 0.5 : 1)
50 | }
51 | }
52 |
53 | // MARK: - Previews
54 |
55 | struct HelpButton_Previews: PreviewProvider {
56 | static var previews: some View {
57 | HelpButton {
58 | Text("Test")
59 | .padding()
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Icon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public enum Icon: String, CaseIterable {
9 | case unread = "tray.2"
10 | case reply = "arrowshape.turn.up.left.2"
11 | case directMessage = "message"
12 | case addressBook = "text.book.closed"
13 | case group = "circle.grid.2x2"
14 |
15 | public var image: Image {
16 | Image(systemName: self.rawValue)
17 | }
18 | }
19 |
20 | struct Icon_Previews: PreviewProvider {
21 | static var previews: some View {
22 | List {
23 | ForEach(Icon.allCases, id: \.rawValue) { icon in
24 | Label(String(describing: icon), systemImage: icon.rawValue)
25 | }
26 | }
27 | .frame(width: 200)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/LevelIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Cocoa
7 | import SwiftUI
8 |
9 | public struct LevelIndicator: View {
10 | private let minimumValue, maximumValue: Double
11 | private let warningValue, criticalValue: Double
12 | private let tickMarkFactor: Double
13 | private let currentValue: Double
14 |
15 | public init(
16 | currentValue: Double,
17 | minimumValue: Double = 0.0,
18 | maximumValue: Double = 1.0,
19 | warningValue: Double = 0.5,
20 | criticalValue: Double = 0.75,
21 | tickMarkFactor: Double = 6.0
22 | ) {
23 | self.minimumValue = minimumValue
24 | self.maximumValue = maximumValue
25 | self.warningValue = warningValue
26 | self.criticalValue = criticalValue
27 | self.tickMarkFactor = tickMarkFactor
28 | self.currentValue = currentValue
29 | }
30 |
31 | public var body: some View {
32 | let indicator = NSLevelIndicator()
33 |
34 | // Configure bounds
35 | indicator.minValue = self.minimumValue * self.tickMarkFactor
36 | indicator.maxValue = self.maximumValue * self.tickMarkFactor
37 | indicator.warningValue = self.warningValue * self.tickMarkFactor
38 | indicator.criticalValue = self.criticalValue * self.tickMarkFactor
39 |
40 | // Apply value
41 | indicator.doubleValue = self.currentValue * self.tickMarkFactor
42 |
43 | return ViewWrap(indicator)
44 | }
45 | }
46 |
47 | struct LevelIndicator_Previews: PreviewProvider {
48 | private struct Preview: View {
49 | var body: some View {
50 | VStack {
51 | ForEach([-2, 0, 0.2, 0.4, 0.6, 0.8, 1, 2], id: \.self) { value in
52 | LevelIndicator(
53 | currentValue: value
54 | )
55 | }
56 | }
57 | .padding()
58 | }
59 | }
60 |
61 | static var previews: some View {
62 | Preview()
63 | .preferredColorScheme(.light)
64 | .previewDisplayName("Light")
65 |
66 | Preview()
67 | .preferredColorScheme(.dark)
68 | .previewDisplayName("Dark")
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/OnlineStatusIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import Assets
8 | import SwiftUI
9 |
10 | public struct OnlineStatusIndicator: View {
11 | let availability: Availability
12 |
13 | public init(_ availability: Availability) {
14 | self.availability = availability
15 | }
16 |
17 | public var body: some View {
18 | LEDIndicator(isOn: self.availability != .unavailable)
19 | .fillColor(self.availability == .available ? Colors.State.green.color : Color.orange)
20 | // Override `LEDIndicator` accessibility label as it says "on"/"off"
21 | .accessibilityElement(children: .ignore)
22 | // FIXME: Localize ProseCoreFFI.Availability
23 | .accessibilityLabel(String(describing: self.availability))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/ReactionPicker/ReactionPickerReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 | import Foundation
9 |
10 | public struct ReactionPickerReducer: ReducerProtocol {
11 | public struct State: Equatable {
12 | let reactions: [Emoji] = "👋👉👍😂😢😭😍😘😊🤯❤️🙏😛🚀⚠️😀😌😇🙃🙂🤩🥳🤨🙁😳🤔😐👀✅❌"
13 | .map { Emoji(String($0)) }
14 | var selected: Set
15 |
16 | let columnCount = 5
17 | let fontSize: CGFloat = 24
18 | let spacing: CGFloat = 4
19 |
20 | var width: CGFloat { self.fontSize * 1.5 }
21 | var height: CGFloat { self.width }
22 |
23 | public init(selected: Set = []) {
24 | self.selected = selected
25 | }
26 | }
27 |
28 | public enum Action: Equatable {
29 | case select(Emoji)
30 | case deselect(Emoji)
31 | }
32 |
33 | public init() {}
34 |
35 | public var body: some ReducerProtocol {
36 | Reduce { state, action in
37 | switch action {
38 | case let .select(reaction):
39 | state.selected.insert(reaction)
40 | return .none
41 | case let .deselect(reaction):
42 | state.selected.remove(reaction)
43 | return .none
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/SpotlightGroupBoxStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public extension GroupBoxStyle where Self == SpotlightGroupBoxStyle {
9 | static var spotlight: Self { SpotlightGroupBoxStyle() }
10 | }
11 |
12 | public struct SpotlightGroupBoxStyle: GroupBoxStyle {
13 | public func makeBody(configuration: Configuration) -> some View {
14 | VStack(spacing: 12) {
15 | configuration.label
16 | .padding(.horizontal, 4)
17 | VStack(spacing: 6) {
18 | configuration.content
19 | .modifier(SpotlightItemBackground())
20 | }
21 | }
22 | }
23 | }
24 |
25 | private struct SpotlightItemBackground: ViewModifier {
26 | var shape: RoundedRectangle {
27 | RoundedRectangle(cornerRadius: 3)
28 | }
29 |
30 | func body(content: Content) -> some View {
31 | content
32 | .padding(.vertical, 8)
33 | .padding(.horizontal, 16)
34 | .background {
35 | self.shape
36 | .fill(.background)
37 | .shadow(color: .gray.opacity(0.5), radius: 1)
38 | }
39 | }
40 | }
41 |
42 | #if DEBUG
43 | import PreviewAssets
44 |
45 | struct SpotlightGroupBoxStyle_Previews: PreviewProvider {
46 | static var previews: some View {
47 | GroupBox {
48 | Text("GroupBox Content goes here")
49 | } label: {
50 | HStack {
51 | Label("support", systemImage: "circle.grid.2x2")
52 | .labelStyle(.coloredIcon)
53 | .font(.title2.bold())
54 | Spacer()
55 | Text("dummy")
56 | .foregroundColor(.secondary)
57 | }
58 | }
59 | .groupBoxStyle(.spotlight)
60 | }
61 | }
62 |
63 | struct SpotlightItemBackground_Previews: PreviewProvider {
64 | static var previews: some View {
65 | Text("GroupBox Content goes here")
66 | .modifier(SpotlightItemBackground())
67 | .padding()
68 | }
69 | }
70 | #endif
71 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "prose-ui")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Toolbar/CommonToolbar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct CommonToolbar: ToolbarContent {
9 | public init() {}
10 |
11 | public var body: some ToolbarContent {
12 | ToolbarItemGroup(placement: .navigation) {
13 | CommonToolbarNavigation()
14 | }
15 |
16 | ToolbarItemGroup {
17 | CommonToolbarActions()
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Toolbar/CommonToolbarActions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct CommonToolbarActions: View {
9 | @Environment(\.redactionReasons) private var redactionReasons
10 |
11 | public init() {}
12 |
13 | public var body: some View {
14 | Button { logger.info("Search tapped") } label: {
15 | Label("Search", systemImage: "magnifyingglass")
16 | }
17 | .unredacted()
18 | .disabled(self.redactionReasons.contains(.placeholder))
19 | // https://github.com/prose-im/prose-app-macos/issues/48
20 | .disabled(true)
21 | }
22 | }
23 |
24 | // MARK: - Previews
25 |
26 | struct CommonToolbarActions_Previews: PreviewProvider {
27 | private struct Preview: View {
28 | var body: some View {
29 | CommonToolbarActions()
30 | .padding()
31 | .previewLayout(.sizeThatFits)
32 | }
33 | }
34 |
35 | static var previews: some View {
36 | Preview()
37 | Preview()
38 | .preferredColorScheme(.dark)
39 | .previewDisplayName("Dark mode")
40 | Preview()
41 | .redacted(reason: .placeholder)
42 | .previewDisplayName("Placeholder")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Toolbar/CommonToolbarNavigation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct CommonToolbarNavigation: View {
9 | @Environment(\.redactionReasons) private var redactionReasons
10 |
11 | public init() {}
12 |
13 | public var body: some View {
14 | Group {
15 | Button { logger.info("Navigation back tapped") } label: {
16 | Label("Back", systemImage: "chevron.backward")
17 | }
18 | Button { logger.info("Navigation forward tapped") } label: {
19 | Label("Forward", systemImage: "chevron.forward")
20 | }
21 | Menu {
22 | // TODO: Add actions
23 | Text("TODO")
24 | } label: {
25 | Label("History", systemImage: "clock")
26 | }
27 | }
28 | .unredacted()
29 | .disabled(self.redactionReasons.contains(.placeholder))
30 | // https://github.com/prose-im/prose-app-macos/issues/48
31 | .disabled(true)
32 | }
33 | }
34 |
35 | // MARK: - Previews
36 |
37 | struct CommonToolbarNavigation_Previews: PreviewProvider {
38 | private struct Preview: View {
39 | var body: some View {
40 | HStack {
41 | CommonToolbarNavigation()
42 | }
43 | .padding()
44 | .previewLayout(.sizeThatFits)
45 | }
46 | }
47 |
48 | static var previews: some View {
49 | Preview()
50 | Preview()
51 | .preferredColorScheme(.dark)
52 | .previewDisplayName("Dark mode")
53 | Preview()
54 | .redacted(reason: .placeholder)
55 | .previewDisplayName("Placeholder")
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/Toolbar/ToolbarDivider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct ToolbarDivider: View {
9 | public init() {}
10 |
11 | public var body: some View {
12 | HStack {
13 | Divider()
14 | .frame(height: 24)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/ProseUI/ViewWrap+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Cocoa
7 | import Foundation
8 | import SwiftUI
9 |
10 | // This wraps any AppKit (NS[..]) component and transforms it into a SwiftUI-compatible View
11 | extension ViewWrap {
12 | init(
13 | _ makeView: @escaping @autoclosure () -> Wrapped,
14 | updater update: @escaping (Wrapped) -> Void
15 | ) {
16 | self.makeView = makeView
17 | self.update = { view, _ in update(view) }
18 | }
19 |
20 | init(_ makeView: @escaping @autoclosure () -> Wrapped) {
21 | self.makeView = makeView
22 | self.update = { _, _ in }
23 | }
24 | }
25 |
26 | struct ViewWrap: NSViewRepresentable {
27 | typealias Updater = (Wrapped, Context) -> Void
28 |
29 | var makeView: () -> Wrapped
30 | var update: (Wrapped, Context) -> Void
31 |
32 | init(
33 | _ makeView: @escaping @autoclosure () -> Wrapped,
34 | updater update: @escaping Updater
35 | ) {
36 | self.makeView = makeView
37 | self.update = update
38 | }
39 |
40 | func makeNSView(context _: Context) -> Wrapped {
41 | self.makeView()
42 | }
43 |
44 | func updateNSView(_ view: Wrapped, context: Context) {
45 | self.update(view, context)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/AccountSettings/AccountSettingsAccountView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import SwiftUI
8 |
9 | private let l10n = L10n.Settings.Accounts.self
10 |
11 | struct AccountSettingsAccountView: View {
12 | @AppStorage("settings.accounts.x.account.enabled") var enabled = true
13 | @AppStorage("settings.accounts.x.account.username") var username = ""
14 | @State var password = ""
15 |
16 | var body: some View {
17 | VStack(spacing: 24) {
18 | GroupBox(l10n.enabledLabel) {
19 | Toggle("", isOn: self.$enabled)
20 | .toggleStyle(.switch)
21 | .labelsHidden()
22 | .disabled(true)
23 | }
24 |
25 | GroupBox(l10n.statusLabel) {
26 | HStack(spacing: 4) {
27 | ConnectionStatusIndicator(status: .connected)
28 | Text(l10n.statusConnected)
29 | .font(.system(size: 13))
30 | .fontWeight(.semibold)
31 | }
32 | }
33 |
34 | VStack {
35 | GroupBox(l10n.addressLabel) {
36 | TextField("", text: self.$username, prompt: Text(l10n.addressPlaceholder))
37 | .textContentType(.username)
38 | .disableAutocorrection(true)
39 | }
40 |
41 | GroupBox(l10n.passwordLabel) {
42 | SecureField("", text: self.$password, prompt: Text(l10n.passwordPlaceholder))
43 | .textContentType(.password)
44 | }
45 | }
46 |
47 | Spacer()
48 | }
49 | .groupBoxStyle(FormGroupBoxStyle(
50 | firstColumnWidth: SettingsConstants
51 | .firstFormColumnConstrainedWidth
52 | ))
53 | .padding()
54 | }
55 | }
56 |
57 | struct AccountSettingsAccountView_Previews: PreviewProvider {
58 | static var previews: some View {
59 | AccountSettingsAccountView()
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/AccountSettings/AccountSettingsFeaturesView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct AccountSettingsFeaturesView: View {
9 | var body: some View {
10 | VStack(spacing: 24) {
11 | // TODO: Add content here
12 | Text("(Server features automated check, i.e. no setting here.)")
13 | Spacer()
14 | }
15 | .padding()
16 | }
17 | }
18 |
19 | struct AccountSettingsFeaturesView_Previews: PreviewProvider {
20 | static var previews: some View {
21 | AccountSettingsFeaturesView()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/AccountSettings/AccountSettingsSecurityView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct AccountSettingsSecurityView: View {
9 | var body: some View {
10 | VStack(spacing: 24) {
11 | // TODO: Add content here
12 | Text("(Server security automated check, i.e. no setting here.)")
13 | Spacer()
14 | }
15 | .padding()
16 | }
17 | }
18 |
19 | struct AccountSettingsSecurityView_Previews: PreviewProvider {
20 | static var previews: some View {
21 | AccountSettingsSecurityView()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Atoms/AccountPickerRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseUI
7 | import SwiftUI
8 |
9 | struct AccountPickerRow: View {
10 | struct ViewModel: Equatable, Identifiable {
11 | let teamLogo: String
12 | let teamDomain: String
13 | let userName: String
14 | var id: String { self.teamDomain }
15 | }
16 |
17 | let viewModel: ViewModel
18 |
19 | var body: some View {
20 | HStack {
21 | Avatar(.placeholder, size: 32)
22 | VStack(alignment: .leading, spacing: 2) {
23 | Text(verbatim: self.viewModel.userName)
24 | .font(.headline)
25 | .foregroundColor(.primary)
26 |
27 | Text(verbatim: self.viewModel.teamDomain)
28 | .font(.subheadline)
29 | .foregroundColor(.secondary)
30 | }
31 | .frame(maxWidth: .infinity, alignment: .leading)
32 | }
33 | }
34 | }
35 |
36 | struct AccountPickerRow_Previews: PreviewProvider {
37 | static var previews: some View {
38 | AccountPickerRow(viewModel: .init(
39 | teamLogo: "logo-crisp",
40 | teamDomain: "crisp.chat",
41 | userName: "Baptiste"
42 | ))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Atoms/ConnectionStatusIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Assets
7 | import SwiftUI
8 |
9 | enum ConnectionStatus: Hashable, CaseIterable {
10 | case disconnected, connected
11 |
12 | var fillColor: Color {
13 | switch self {
14 | case .connected:
15 | return Colors.State.greenLight.color
16 | case .disconnected:
17 | return Colors.State.greyLight.color
18 | }
19 | }
20 | }
21 |
22 | struct ConnectionStatusIndicator: View {
23 | private let status: ConnectionStatus
24 | private let size: CGFloat
25 |
26 | init(
27 | status: ConnectionStatus = .disconnected,
28 | size: CGFloat = 10.0
29 | ) {
30 | self.status = status
31 | self.size = size
32 | }
33 |
34 | init(_ status: ConnectionStatus) {
35 | self.init(status: status)
36 | }
37 |
38 | var body: some View {
39 | Circle()
40 | .fill(self.status.fillColor)
41 | .frame(width: self.size, height: self.size)
42 | }
43 | }
44 |
45 | struct ConnectionStatusIndicator_Previews: PreviewProvider {
46 | private struct Preview: View {
47 | var body: some View {
48 | HStack {
49 | ForEach(
50 | ConnectionStatus.allCases,
51 | id: \.self,
52 | content: ConnectionStatusIndicator.init(_:)
53 | )
54 | }
55 | .padding()
56 | }
57 | }
58 |
59 | static var previews: some View {
60 | Preview()
61 | .preferredColorScheme(.light)
62 | .previewDisplayName("Light")
63 |
64 | Preview()
65 | .preferredColorScheme(.dark)
66 | .previewDisplayName("Dark")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Atoms/FormGroupBoxStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | /// Because it is cross-platform, SwiftUI doesn't really like the macOS form style with two columns.
9 | /// It supports it, but we cannot put labels in the first column if they're not attached to a single
10 | /// control.
11 | /// To work around it, this `GroupBoxStyle` mimics the macOS form style.
12 | ///
13 | /// - Note: This style might break some layout, accessibility and readability benefits of the system
14 | /// style.
15 | struct FormGroupBoxStyle: GroupBoxStyle {
16 | let firstColumnWidth: CGFloat
17 | init(firstColumnWidth: CGFloat = SettingsConstants.firstFormColumnWidth) {
18 | self.firstColumnWidth = firstColumnWidth
19 | }
20 |
21 | func makeBody(configuration: Configuration) -> some View {
22 | HStack(alignment: .top) {
23 | VStack(alignment: .trailing) {
24 | configuration.label
25 | }
26 | .frame(width: self.firstColumnWidth, alignment: .trailing)
27 | VStack(alignment: .leading) {
28 | configuration.content
29 | }
30 | .textFieldStyle(.roundedBorder)
31 | .frame(maxWidth: .infinity, alignment: .leading)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Atoms/VideoPreviewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct VideoPreviewView: View {
9 | @State var streamPath: String
10 | @State var sizeWidth: CGFloat = 180
11 | @State var sizeHeight: CGFloat = 100
12 |
13 | var body: some View {
14 | Image(self.streamPath)
15 | .resizable()
16 | .aspectRatio(contentMode: .fit)
17 | .frame(width: self.sizeWidth, height: self.sizeHeight)
18 | .background(.black)
19 | .cornerRadius(4)
20 | .clipped()
21 | }
22 | }
23 |
24 | struct VideoPreviewView_Previews: PreviewProvider {
25 | static var previews: some View {
26 | VideoPreviewView(
27 | streamPath: "webcam-valerian",
28 | sizeWidth: 260,
29 | sizeHeight: 180
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/SettingsConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import CoreGraphics
7 |
8 | enum SettingsConstants {
9 | static let contentWidth: CGFloat = 640
10 | static let firstFormColumnWidth: CGFloat = 192
11 | static let firstFormColumnConstrainedWidth: CGFloat = 128
12 | static let selectLargeWidth: CGFloat = 256
13 | static let selectNormalWidth: CGFloat = 192
14 | static let selectSmallWidth: CGFloat = 96
15 | }
16 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/SettingsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import SwiftUI
8 |
9 | public struct SettingsView: View {
10 | enum Tabs: Hashable {
11 | case general, accounts, notifications, messages, calls, advanced
12 | }
13 |
14 | @State private var tab: Tabs = .general
15 |
16 | public init() {}
17 | public var body: some View {
18 | TabView(selection: self.$tab) {
19 | GeneralTab()
20 | .tabItem { Label(L10n.Settings.Tabs.general, systemImage: "gearshape") }
21 | .tag(Tabs.general)
22 | AccountsTab()
23 | .tabItem { Label(L10n.Settings.Tabs.accounts, systemImage: "person.2") }
24 | .tag(Tabs.accounts)
25 | NotificationsTab()
26 | .tabItem { Label(L10n.Settings.Tabs.notifications, systemImage: "bell") }
27 | .tag(Tabs.notifications)
28 | MessagesTab()
29 | .tabItem {
30 | Label(L10n.Settings.Tabs.messages, systemImage: "bubble.left.and.bubble.right")
31 | }
32 | .tag(Tabs.messages)
33 | CallsTab()
34 | .tabItem { Label(L10n.Settings.Tabs.calls, systemImage: "phone.arrow.up.right") }
35 | .tag(Tabs.calls)
36 | AdvancedTab()
37 | .tabItem { Label(L10n.Settings.Tabs.advanced, systemImage: "dial.min") }
38 | .tag(Tabs.advanced)
39 | }
40 | .frame(width: SettingsConstants.contentWidth)
41 | .fixedSize(horizontal: true, vertical: false)
42 | }
43 | }
44 |
45 | struct SettingsView_Previews: PreviewProvider {
46 | static var previews: some View {
47 | SettingsView()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Tabs/AdvancedTab.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import SwiftUI
8 |
9 | private let l10n = L10n.Settings.Advanced.self
10 |
11 | enum AdvancedSettingsUpdateChannel: String, Equatable, CaseIterable {
12 | case stable
13 | case beta
14 |
15 | var localizedDescription: String {
16 | switch self {
17 | case .stable:
18 | return l10n.UpdateChannel.optionStable
19 | case .beta:
20 | return l10n.UpdateChannel.optionBeta
21 | }
22 | }
23 | }
24 |
25 | struct AdvancedTab: View {
26 | @AppStorage(
27 | "settings.advanced.updateChannel"
28 | ) var updateChannel: AdvancedSettingsUpdateChannel =
29 | .stable
30 | @AppStorage("settings.advanced.reportsUsage") var reportsUsage = true
31 | @AppStorage("settings.advanced.reportsCrash") var reportsCrash = true
32 |
33 | var body: some View {
34 | VStack(spacing: 24) {
35 | // "Update channel"
36 | GroupBox(l10n.UpdateChannel.label) {
37 | Picker("", selection: self.$updateChannel) {
38 | ForEach(AdvancedSettingsUpdateChannel.allCases, id: \.self) { value in
39 | Text(value.localizedDescription)
40 | .tag(value)
41 | }
42 | }
43 | .labelsHidden()
44 | .frame(width: SettingsConstants.selectNormalWidth)
45 | }
46 |
47 | // "Reports"
48 | GroupBox(l10n.Reports.label) {
49 | Toggle(l10n.Reports.usageToggle, isOn: self.$reportsUsage)
50 | Toggle(l10n.Reports.crashToggle, isOn: self.$reportsCrash)
51 | }
52 | }
53 | .groupBoxStyle(FormGroupBoxStyle())
54 | .padding()
55 | .disabled(true)
56 | }
57 | }
58 |
59 | struct AdvancedTab_Previews: PreviewProvider {
60 | static var previews: some View {
61 | AdvancedTab()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SettingsFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "settings")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Atoms/Counter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct Counter: View {
9 | var count = 0
10 |
11 | var body: some View {
12 | // We're enabling the isEmpty rule due to a bug in Swiftformat where it thinks that we're
13 | // accessing the count property of a Collection.
14 | // swiftformat:disable isEmpty
15 | if self.count > 0 {
16 | Text(self.count, format: .number)
17 | .font(.system(size: 11, weight: .semibold))
18 | .padding(.vertical, 2)
19 | .padding(.horizontal, 5)
20 | .foregroundColor(.secondary)
21 | .background {
22 | Capsule()
23 | .fill(.quaternary)
24 | }
25 | // swiftformat:enable isEmpty
26 | }
27 | }
28 | }
29 |
30 | struct Counter_Previews: PreviewProvider {
31 | private struct Preview: View {
32 | private static let values = [0, 2, 10, 1000]
33 |
34 | var body: some View {
35 | VStack {
36 | ForEach(Self.values, id: \.self) { count in
37 | HStack {
38 | Text(count.description)
39 | .unredacted()
40 | Spacer()
41 | Counter(count: count)
42 | }
43 | }
44 | }
45 | .frame(width: 128)
46 | .padding()
47 | }
48 | }
49 |
50 | static var previews: some View {
51 | Preview()
52 | .preferredColorScheme(.light)
53 | .previewDisplayName("Light")
54 | Preview()
55 | .preferredColorScheme(.dark)
56 | .previewDisplayName("Dark")
57 | Preview()
58 | .redacted(reason: .placeholder)
59 | .previewDisplayName("Placeholder")
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Footer/AccountSettingsMenu/AccountSettingsMenuReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 | import CredentialsClient
9 | import Foundation
10 | import ProseCore
11 |
12 | public struct AccountSettingsMenuReducer: ReducerProtocol {
13 | public typealias State = SessionState
14 |
15 | public struct AccountSettingsMenuState: Equatable {
16 | var statusIcon: Character
17 | }
18 |
19 | public enum Action: Equatable {
20 | case accountSettingsTapped
21 | case changeAvailabilityTapped(Availability)
22 | case editProfileTapped
23 | case offlineModeTapped
24 | case pauseNotificationsTapped
25 | case signOutTapped
26 | case updateMoodTapped
27 | }
28 |
29 | @Dependency(\.accountBookmarksClient) var accountBookmarks
30 | @Dependency(\.accountsClient) var accounts
31 | @Dependency(\.credentialsClient) var credentials
32 |
33 | public var body: some ReducerProtocol {
34 | Reduce { state, action in
35 | switch action {
36 | case .signOutTapped:
37 | return .fireAndForget { [jid = state.selectedAccountId] in
38 | try? await self.accountBookmarks.removeBookmark(jid)
39 | try? self.credentials.deleteCredentials(jid)
40 | self.accounts.removeAccount(jid)
41 | }
42 |
43 | case .accountSettingsTapped:
44 | return .none
45 |
46 | case let .changeAvailabilityTapped(availability):
47 | state.selectedAccount.availability = availability
48 | return .none
49 |
50 | case .editProfileTapped:
51 | return .none
52 |
53 | case .offlineModeTapped:
54 | return .none
55 |
56 | case .pauseNotificationsTapped:
57 | return .none
58 |
59 | case .updateMoodTapped:
60 | return .none
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Footer/AccountSwitcherMenu/AccountSwitcherMenuReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 |
9 | public struct AccountSwitcherMenuReducer: ReducerProtocol {
10 | public typealias State = SessionState
11 |
12 | public struct AccountSwitcherMenuState: Equatable {
13 | public init() {}
14 | }
15 |
16 | public enum Action: Equatable {
17 | case showMenuTapped
18 | case accountSelected(BareJid)
19 | case connectAccountTapped
20 | /// Only here for accessibility
21 | case manageServerTapped
22 | }
23 |
24 | public init() {}
25 |
26 | public var body: some ReducerProtocol {
27 | EmptyReducer()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Footer/FooterDetails.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppLocalization
7 | import Assets
8 | import SwiftUI
9 |
10 | struct FooterDetails: View {
11 | let teamName: String
12 | let statusIcon: Character
13 | let statusMessage: String
14 |
15 | var body: some View {
16 | VStack(alignment: .leading, spacing: 4) {
17 | Text(self.teamName)
18 | .font(.system(size: 12, weight: .bold))
19 | .foregroundColor(Colors.Text.primary.color)
20 |
21 | Text("\(String(self.statusIcon)) “\(self.statusMessage)”")
22 | .font(.system(size: 11))
23 | .foregroundColor(Colors.Text.secondary.color)
24 | .layoutPriority(1)
25 | }
26 | // Make hit box full width
27 | .frame(maxWidth: .infinity, alignment: .leading)
28 | .contentShape([.interaction, .focusEffect], Rectangle())
29 | .accessibilityElement(children: .ignore)
30 | .accessibilityLabel("\(L10n.Server.ConnectedTo.label(self.teamName)), \(self.statusMessage)")
31 | }
32 | }
33 |
34 | struct FooterDetails_Previews: PreviewProvider {
35 | private struct Preview: View {
36 | var body: some View {
37 | FooterDetails(
38 | teamName: "Crisp",
39 | statusIcon: "🚀",
40 | statusMessage: "Building new stuff."
41 | )
42 | .frame(maxWidth: 256)
43 | .padding()
44 | .previewLayout(.sizeThatFits)
45 | }
46 | }
47 |
48 | static var previews: some View {
49 | Preview()
50 | .preferredColorScheme(.light)
51 | .previewDisplayName("Light")
52 | Preview()
53 | .preferredColorScheme(.dark)
54 | .previewDisplayName("Dark")
55 | Preview()
56 | .redacted(reason: .placeholder)
57 | .previewDisplayName("Placeholder")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Footer/Styles/SidebarFooterPopoverButtonStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct SidebarFooterPopoverButtonStyle: ButtonStyle {
9 | @Environment(\.isEnabled) private var isEnabled
10 | func makeBody(configuration: Configuration) -> some View {
11 | configuration.label
12 | .opacity((configuration.isPressed || !self.isEnabled) ? 0.5 : 1)
13 | .foregroundColor(Self.color(for: configuration.role))
14 | // Make hit box full width
15 | // NOTE: [Rémi Bardon] We could avoid making the hit box full width for destructive actions.
16 | .frame(maxWidth: .infinity, alignment: .leading)
17 | // Allow hits in the transparent areas
18 | .contentShape(Rectangle())
19 | }
20 |
21 | static func color(for role: ButtonRole?) -> Color? {
22 | switch role {
23 | case .some(.destructive):
24 | return .red
25 | default:
26 | return nil
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Footer/Styles/VStackGroupBoxStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct VStackGroupBoxStyle: GroupBoxStyle {
9 | let alignment: HorizontalAlignment
10 | let spacing: CGFloat?
11 |
12 | func makeBody(configuration: Configuration) -> some View {
13 | VStack(alignment: self.alignment, spacing: self.spacing) { configuration.content }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Rows/ActionButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct ActionButton: View {
9 | let title: String
10 | let action: () -> Void
11 |
12 | var body: some View {
13 | Button(action: self.action) {
14 | Label(self.title, systemImage: "plus.square.fill")
15 | .symbolVariant(.fill)
16 | // Make hit box full width
17 | .frame(maxWidth: .infinity, alignment: .leading)
18 | .contentShape(.interaction, Rectangle())
19 | }
20 | .buttonStyle(.plain)
21 | }
22 | }
23 |
24 | struct ActionRow_Previews: PreviewProvider {
25 | static var previews: some View {
26 | ActionButton(
27 | title: "sidebar_groups_add",
28 | action: {}
29 | )
30 | .frame(width: 196)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Rows/ContactRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ProseUI
8 | import SwiftUI
9 |
10 | struct ContactRow: View {
11 | var title: String
12 | var avatar: AvatarImage
13 | var count = 0
14 | var status: Availability = .unavailable
15 |
16 | var body: some View {
17 | HStack {
18 | Avatar(self.avatar, size: 18)
19 |
20 | HStack(alignment: .firstTextBaseline, spacing: 4) {
21 | Text(self.title)
22 |
23 | OnlineStatusIndicator(self.status)
24 | }
25 |
26 | Spacer()
27 |
28 | Counter(count: self.count)
29 | }
30 | }
31 | }
32 |
33 | #if DEBUG
34 | import PreviewAssets
35 |
36 | struct ContactRow_Previews: PreviewProvider {
37 | private struct Preview: View {
38 | var body: some View {
39 | ContactRow(
40 | title: "Valerian",
41 | avatar: AvatarImage(url: PreviewAsset.Avatars.valerian.customURL),
42 | count: 3
43 | )
44 | .frame(width: 196)
45 | .padding()
46 | }
47 | }
48 |
49 | static var previews: some View {
50 | Preview()
51 | .preferredColorScheme(.light)
52 | .previewDisplayName("Light")
53 |
54 | Preview()
55 | .preferredColorScheme(.dark)
56 | .previewDisplayName("Dark")
57 | }
58 | }
59 | #endif
60 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Rows/IconRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseUI
7 | import SwiftUI
8 |
9 | struct IconRow: View {
10 | var title: String
11 | var icon: Icon
12 | var count = 0
13 |
14 | var body: some View {
15 | HStack {
16 | Label(self.title, systemImage: self.icon.rawValue)
17 | .frame(maxWidth: .infinity, alignment: .leading)
18 | Counter(count: self.count)
19 | }
20 | }
21 | }
22 |
23 | struct NavigationRow_Previews: PreviewProvider {
24 | static var previews: some View {
25 | IconRow(
26 | title: "Label",
27 | icon: .unread,
28 | count: 10
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/SidebarFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "sidebar")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHelpers/Date+YMD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Date {
9 | static func ymd(
10 | _ year: Int,
11 | _ month: Int,
12 | _ day: Int,
13 | _ hour: Int = 0,
14 | _ minute: Int = 0,
15 | _ second: Int = 0,
16 | timeZone: TimeZone? = nil
17 | ) -> Date {
18 | let components = DateComponents(
19 | timeZone: timeZone,
20 | year: year,
21 | month: month,
22 | day: day,
23 | hour: hour,
24 | minute: minute,
25 | second: second
26 | )
27 | return Calendar.current.date(from: components)!
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHelpers/UUID+Incrementing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | public extension UUID {
9 | /// A deterministic, auto-incrementing "UUID" generator for testing.
10 | static var incrementing: () -> UUID {
11 | var uuid = 0
12 | return {
13 | defer { uuid += 1 }
14 | return UUID(uuidString: "00000000-0000-0000-0000-\(String(format: "%012x", uuid))")!
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHostApp/Helpers/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "test-host-app")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHostApp/Helpers/TestScene.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import SwiftUI
8 |
9 | public struct TestScene: Scene {
10 | public init() {
11 | if ProcessInfo.processInfo.environment["is-running-ui-test"] == "1" {
12 | NSScrollView.prose_prepareForUITest()
13 | }
14 | }
15 |
16 | public var body: some Scene {
17 | WindowGroup {
18 | Group {
19 | switch ProcessInfo.processInfo.environment["test-case"] {
20 | case "roster-selection":
21 | RosterSelection()
22 |
23 | case "conversation-info":
24 | RosterSelection()
25 |
26 | default:
27 | Text("""
28 | Missing or unknown test case.
29 |
30 | If you're trying to run a UI Test, make sure to specify the desired testcase in \
31 | your XCUIApplication with:
32 |
33 | app.launchEnvironment = ["test-case": "roster-selection"]
34 |
35 | If you're trying to run the app manually, add a Environment Variable "test-case" to \
36 | the current scheme.
37 | """)
38 | }
39 | }
40 | .ignoresSafeArea()
41 | .preferredColorScheme(
42 | ProcessInfo.processInfo.environment["dark-mode-enabled"] == "1" ? .dark : .light
43 | )
44 | }
45 | }
46 | }
47 |
48 | private func SwizzleImplementations(
49 | in obj: AnyClass,
50 | originalSelector: Selector,
51 | swizzledSelector: Selector
52 | ) {
53 | if
54 | let originalMethod = class_getInstanceMethod(obj, originalSelector),
55 | let swizzledMethod = class_getInstanceMethod(obj, swizzledSelector)
56 | {
57 | method_exchangeImplementations(originalMethod, swizzledMethod)
58 | }
59 | }
60 |
61 | extension NSScrollView {
62 | public static func prose_prepareForUITest() {
63 | SwizzleImplementations(
64 | in: NSScrollView.self,
65 | originalSelector: #selector(flashScrollers),
66 | swizzledSelector: #selector(prose_flashScrollers)
67 | )
68 | }
69 |
70 | @objc func prose_flashScrollers() {
71 | // Do nothing…
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHostApp/Mocks/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | @testable import App
7 | import AppDomain
8 | import Foundation
9 | import Mocks
10 |
11 | extension AppReducer.State {
12 | /// Returns an `AppState` with jane.doe@prose.org logged in.
13 | static let authenticated: AppReducer.State = {
14 | var state = AppReducer.State()
15 | state.initialized = true
16 | state.selectedAccountId = .janeDoe
17 | state.availableAccounts = [
18 | .init(
19 | jid: .janeDoe,
20 | status: .connected,
21 | settings: .init(availability: .available),
22 | contacts: {
23 | let contacts = Contact.random()
24 | return Dictionary(
25 | zip(contacts.map(\.jid), contacts),
26 | uniquingKeysWith: { _, last in last }
27 | )
28 | }()
29 | ),
30 | ]
31 | state.mainState = .init()
32 | return state
33 | }()
34 | }
35 |
36 | extension Contact {
37 | static func random(count: Int = 5) -> [Contact] {
38 | RNDResult.all()
39 | .prefix(count)
40 | .map { user in
41 | Contact(
42 | jid: BareJid(stringLiteral: user.email),
43 | name: "\(user.name.first) \(user.name.last)",
44 | avatar: nil,
45 | availability: .available,
46 | status: nil,
47 | groups: ["Contacts"]
48 | )
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/TestHostApp/TestCases/RosterSelection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | @testable import App
7 | import Combine
8 | import ComposableArchitecture
9 | @testable import ProseCore
10 | import SwiftUI
11 | import Toolbox
12 |
13 | struct RosterSelection: View {
14 | let store: StoreOf
15 |
16 | init() {
17 | var coreClient = ProseCoreClient.noop
18 | coreClient.loadLatestMessages = { jid, _, _ in
19 | // Only take the node so that the WebView doesn't highlight the URL which creates a separate
20 | // accessiblity element (a link).
21 | let name = String(jid.rawValue.prefix(while: { $0 != "@" }))
22 | return [Message.mock(from: jid, body: "Hello from \(name)")]
23 | }
24 |
25 | var accountsClient = AccountsClient.noop
26 | accountsClient.client = { _ in coreClient }
27 |
28 | self.store = .init(
29 | initialState: .authenticated,
30 | reducer: AppReducer(),
31 | prepareDependencies: { deps in
32 | deps.accountBookmarksClient = .testValue
33 | deps.accountsClient = accountsClient
34 | deps.connectivityClient = .previewValue
35 | deps.credentialsClient = .noop
36 | deps.openURL = .init(handler: { url in
37 | logger.info("Not opening url \(url.absoluteString) in UITest.")
38 | return true
39 | })
40 | }
41 | )
42 | }
43 |
44 | public var body: some View {
45 | AppView(store: self.store)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Toolbox/AsyncStream+Prose.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | // Source: https://github.com/tgrapperon/swift-dependencies-additions/blob/bcb5e934a1b9a7d661ab5a9dce026015d5b03db4/Sources/DependenciesAdditionsBasics/ConcurrencySupport/AsyncStream%2BAdditions.swift
7 |
8 | public extension AsyncStream {
9 | /// Produces an `AsyncStream` from an async `AsyncSequence` by awaiting and then consuming the
10 | /// sequence till it terminates, ignoring any failure.
11 | ///
12 | /// Useful as a kind of type eraser for actor-isolated live `AsyncSequence`-based dependencies,
13 | /// that also erases the `async` extraction.
14 | init(_ sequence: @escaping () async throws -> S) rethrows
15 | where S.Element == Element
16 | {
17 | var iterator: S.AsyncIterator?
18 | self.init {
19 | if iterator == nil {
20 | iterator = try? await sequence().makeAsyncIterator()
21 | }
22 | return try? await iterator?.next()
23 | }
24 | }
25 | }
26 |
27 | public extension AsyncStream {
28 | static func empty() -> Self {
29 | .init(unfolding: { nil })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Toolbox/FocusState+Synchronize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import SwiftUI
7 |
8 | public extension View {
9 | /// Synchronizes a `Binding` and a `FocusState.Binding`.
10 | ///
11 | /// Comes from .
12 | func synchronize(
13 | _ first: Binding,
14 | _ second: FocusState.Binding
15 | ) -> some View {
16 | self
17 | .onChange(of: first.wrappedValue) { second.wrappedValue = $0 }
18 | .onChange(of: second.wrappedValue) { first.wrappedValue = $0 }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Toolbox/ItemProvider+Prose.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Combine
7 | import Foundation
8 |
9 | public extension NSItemProvider {
10 | func prose_loadObject(ofClass: T.Type) async throws -> T? where T: NSItemProviderReading {
11 | try await withCheckedThrowingContinuation { continuation in
12 | _ = loadObject(ofClass: ofClass) { data, error in
13 | if let error {
14 | continuation.resume(throwing: error)
15 | return
16 | }
17 |
18 | guard let image = data as? T else {
19 | continuation.resume(returning: nil)
20 | return
21 | }
22 |
23 | continuation.resume(returning: image)
24 | }
25 | }
26 | }
27 |
28 | func prose_loadImage() async throws -> PlatformImage? {
29 | try await self.prose_loadObject(ofClass: PlatformImage.self)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Toolbox/PlatformImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 |
8 | #if os(macOS)
9 | import AppKit
10 |
11 | public typealias PlatformImage = NSImage
12 |
13 | public extension NSImage {
14 | var cgImage: CGImage? {
15 | self.cgImage(forProposedRect: nil, context: nil, hints: nil)
16 | }
17 |
18 | func jpegData(compressionQuality: CGFloat) -> Data? {
19 | self.cgImage.flatMap { cgImage in
20 | NSBitmapImageRep(cgImage: cgImage).representation(
21 | using: NSBitmapImageRep.FileType.jpeg,
22 | properties: [.compressionFactor: compressionQuality]
23 | )
24 | }
25 | }
26 | }
27 |
28 | #elseif os(iOS) || os(tvOS) || os(watchOS)
29 | import UIKit
30 |
31 | public typealias PlatformImage = UIImage
32 | #endif
33 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/Toolbox/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "toolbox")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/UnreadFeature/Target+Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import OSLog
7 |
8 | internal let logger = Logger(subsystem: "org.prose.app", category: "unread")
9 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/UnreadFeature/Toolbar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import ProseUI
7 | import SwiftUI
8 |
9 | // MARK: - View
10 |
11 | struct Toolbar: ToolbarContent {
12 | @Environment(\.redactionReasons) private var redactionReasons
13 |
14 | var body: some ToolbarContent {
15 | ToolbarItemGroup(placement: .navigation) {
16 | CommonToolbarNavigation()
17 | }
18 | ToolbarItemGroup {
19 | Self.actions(redactionReasons: self.redactionReasons)
20 | ToolbarDivider()
21 | CommonToolbarActions()
22 | }
23 | }
24 |
25 | @ViewBuilder
26 | static func actions(redactionReasons: RedactionReasons) -> some View {
27 | Button { logger.info("Mark as read tapped") } label: {
28 | Label("Mark as read", systemImage: "envelope.open")
29 | }
30 | .unredacted()
31 | .disabled(redactionReasons.contains(.placeholder))
32 | // https://github.com/prose-im/prose-app-macos/issues/48
33 | .disabled(true)
34 |
35 | ToolbarDivider()
36 |
37 | Menu {
38 | // TODO: Add actions
39 | Text("TODO")
40 | } label: {
41 | Label("Filter", systemImage: "line.3.horizontal.decrease.circle")
42 | }
43 | .unredacted()
44 | .disabled(redactionReasons.contains(.placeholder))
45 | // https://github.com/prose-im/prose-app-macos/issues/48
46 | .disabled(true)
47 | }
48 | }
49 |
50 | // MARK: - Previews
51 |
52 | internal struct Toolbar_Previews: PreviewProvider {
53 | private struct Preview: View {
54 | @Environment(\.redactionReasons) private var redactionReasons
55 |
56 | var body: some View {
57 | HStack {
58 | Toolbar.actions(redactionReasons: self.redactionReasons)
59 | }
60 | .padding()
61 | .previewLayout(.sizeThatFits)
62 | }
63 | }
64 |
65 | static var previews: some View {
66 | Preview()
67 | Preview()
68 | .redacted(reason: .placeholder)
69 | .previewDisplayName("Placeholder")
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/UnreadFeature/UnreadScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Assets
7 | import ComposableArchitecture
8 | import ConversationFeature
9 | import ProseUI
10 | import SwiftUI
11 |
12 | public struct UnreadScreen: View {
13 | private let store: StoreOf
14 | private var actions: ViewStore
15 |
16 | public init(store: StoreOf) {
17 | self.store = store
18 | self.actions = ViewStore(store.stateless)
19 | }
20 |
21 | public var body: some View {
22 | self.content()
23 | .frame(maxWidth: .infinity, maxHeight: .infinity)
24 | .background(Colors.Background.message.color)
25 | .toolbar(content: Toolbar.init)
26 | .onAppear { self.actions.send(.onAppear) }
27 | .groupBoxStyle(.spotlight)
28 | }
29 |
30 | private func content() -> some View {
31 | WithViewStore(self.store.scope(state: \.messages.isEmpty)) { noMessage in
32 | if noMessage.state {
33 | self.nothing()
34 | } else {
35 | self.list()
36 | }
37 | }
38 | }
39 |
40 | private func nothing() -> some View {
41 | Text("Looks like you read everything 🎉")
42 | .font(.largeTitle.bold())
43 | .foregroundColor(.secondary)
44 | .padding()
45 | .unredacted()
46 | }
47 |
48 | private func list() -> some View {
49 | ScrollView {
50 | VStack(spacing: 24) {
51 | WithViewStore(self.store.scope(state: \.messages)) { messages in
52 | ForEach(messages.state, id: \.chatId, content: UnreadSection.init(model:))
53 | }
54 | }
55 | .padding(24)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/UnreadFeature/UnreadScreenReducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ComposableArchitecture
8 |
9 | public struct UnreadScreenReducer: ReducerProtocol {
10 | public typealias State = SessionState
11 |
12 | public struct UnreadScreenState: Equatable {
13 | var messages = [UnreadSectionModel]()
14 |
15 | public init() {}
16 | }
17 |
18 | public enum Action: Equatable {
19 | case onAppear
20 | }
21 |
22 | public init() {}
23 |
24 | public var body: some ReducerProtocol {
25 | Reduce { _, action in
26 | switch action {
27 | case .onAppear:
28 | return .none
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Sources/UnreadFeature/UnreadSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ConversationFeature
8 | import ProseUI
9 | import SwiftUI
10 |
11 | public struct UnreadSectionModel: Equatable {
12 | let chatId: BareJid
13 | let chatTitle: String
14 | var messages: [Message]
15 |
16 | public init(
17 | chatId: BareJid,
18 | chatTitle: String,
19 | messages: [Message]
20 | ) {
21 | self.chatId = chatId
22 | self.chatTitle = chatTitle
23 | self.messages = messages
24 | }
25 | }
26 |
27 | struct UnreadSection: View {
28 | @Environment(\.redactionReasons) private var redactionReasons
29 |
30 | let model: UnreadSectionModel
31 |
32 | var body: some View {
33 | GroupBox {
34 | HStack {
35 | VStack {
36 | ForEach(self.model.messages, content: MessageView.init(model:))
37 | }
38 | VStack {
39 | VStack {
40 | Button { logger.info("Reply tapped") } label: {
41 | // FIXME: Localize
42 | Label("Reply", systemImage: "arrowshape.turn.up.right")
43 | .frame(maxWidth: .infinity)
44 | }
45 | .foregroundColor(.accentColor)
46 | .unredacted()
47 | Button { logger.info("Mark read tapped") } label: {
48 | // FIXME: Localize
49 | Text("Mark read")
50 | .frame(maxWidth: .infinity)
51 | }
52 | .unredacted()
53 | }
54 | .frame(width: 96)
55 | .labelStyle(.vertical)
56 | .buttonStyle(.shadowed)
57 | }
58 | }
59 | } label: {
60 | HStack {
61 | Label(self.model.chatTitle, systemImage: Icon.directMessage.rawValue)
62 | .labelStyle(.coloredIcon)
63 | .font(.title3.bold())
64 | Spacer()
65 | Text(self.model.messages.last!.timestamp, format: .relative(presentation: .named))
66 | .foregroundColor(.secondary)
67 | }
68 | }
69 | .disabled(self.redactionReasons.contains(.placeholder))
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Tests/ConversationFeatureTests/Helpers/ChatSessionState+Mock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | import ConversationFeature
8 | import Mocks
9 |
10 | public extension ChatSessionState {
11 | static func mock(
12 | selectedAccountId: BareJid = .janeDoe,
13 | chatId: BareJid = .johnDoe,
14 | userInfos: [BareJid: Contact] = [.johnDoe: .johnDoe],
15 | composingUsers: [BareJid] = [],
16 | _ childState: ChildState
17 | ) -> Self {
18 | .init(
19 | selectedAccountId: selectedAccountId,
20 | chatId: chatId,
21 | userInfos: userInfos,
22 | composingUsers: composingUsers,
23 | childState: childState
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Tests/CredentialsClientTests/CredentialsClientTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppDomain
7 | @testable import CredentialsClient
8 | import XCTest
9 |
10 | final class CredentialsStoreTests: XCTestCase {
11 | func testSavesUpdatesAndDeletesCredentials() throws {
12 | let store = CredentialsClient.live(service: "org.prose.app.tests")
13 |
14 | let jid: BareJid = "tests@prose.org"
15 |
16 | try XCTAssertNil(store.loadCredentials(jid))
17 |
18 | let initialCredentials = Credentials(jid: jid, password: "initial-password")
19 |
20 | try store.save(initialCredentials)
21 |
22 | try XCTAssertEqual(store.loadCredentials(jid), initialCredentials)
23 |
24 | let updatedCredentials = Credentials(jid: jid, password: "updated-passowrd")
25 |
26 | try store.save(updatedCredentials)
27 |
28 | try XCTAssertEqual(store.loadCredentials(jid), updatedCredentials)
29 |
30 | try store.deleteCredentials(jid)
31 |
32 | try XCTAssertNil(store.loadCredentials(jid))
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Prose/ProseLib/Tests/ProseCoreViewsTests/IdentifiedArrayDifferenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import typealias IdentifiedCollections.IdentifiedArrayOf
7 | @testable import ProseCoreViews
8 | import XCTest
9 |
10 | final class IdentifiedArrayDifferenceTests: XCTestCase {
11 | func testDifference() {
12 | struct Item: Equatable, Identifiable, ExpressibleByIntegerLiteral {
13 | let id: Int
14 | var content = UUID()
15 | init(id: Int) {
16 | self.id = id
17 | }
18 |
19 | init(integerLiteral value: IntegerLiteralType) {
20 | self.init(id: Int(value))
21 | }
22 | }
23 |
24 | let items: [Item] = [0, 1, 2, 3]
25 |
26 | let arrayBefore: IdentifiedArrayOf- = [items[0], items[3], items[1]]
27 | var item3 = items[3]
28 | item3.content = UUID()
29 | let arrayAfter: IdentifiedArrayOf
- = [items[0], item3, items[2]]
30 |
31 | let diff = arrayAfter.difference(from: arrayBefore)
32 | XCTAssertEqual(diff.insertedIds, [2])
33 | XCTAssertEqual(diff.removedIds, [1])
34 | XCTAssertEqual(diff.updatedIds, [3])
35 |
36 | let oppositeDiff = arrayBefore.difference(from: arrayAfter)
37 | XCTAssertEqual(oppositeDiff.insertedIds, diff.removedIds)
38 | XCTAssertEqual(oppositeDiff.removedIds, diff.insertedIds)
39 | XCTAssertEqual(oppositeDiff.updatedIds, diff.updatedIds)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Prose/ProseUITests/ConversationInfoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import XCTest
8 |
9 | final class ConversationInfoTests: XCTestCase {
10 | override func setUp() {
11 | self.continueAfterFailure = false
12 | }
13 |
14 | func testInfoChangesWhenSwitchingConversation() {
15 | let app = XCUIApplication.launching(testCase: "conversation-info")
16 |
17 | app.sidebar.cells
18 | .containing(.staticText, identifier: "Oya Karaböcek").element.tap()
19 |
20 | app.toolbars.checkBoxes["Info"].tap()
21 |
22 | XCTAssertTrue(
23 | app.conversationInfo.otherElements["Oya Karaböcek, available"]
24 | .waitForExistence(timeout: 5)
25 | )
26 |
27 | app.sidebar.cells
28 | .containing(.staticText, identifier: "Donna Reed").element.tap()
29 |
30 | app.toolbars.checkBoxes["Info"].tap()
31 |
32 | XCTAssertTrue(
33 | app.conversationInfo.otherElements["Donna Reed, available"]
34 | .waitForExistence(timeout: 5)
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Prose/ProseUITests/Helpers/XCUIApplication+Prose.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import XCTest
8 |
9 | extension XCUIApplication {
10 | static func launching(
11 | testCase: String,
12 | isDarkModeEnabled: Bool = false,
13 | animationsEnabled: Bool = false
14 | ) -> XCUIApplication {
15 | let app = XCUIApplication()
16 | app.launchEnvironment = [
17 | "test-case": testCase,
18 | "is-running-ui-test": "1",
19 | "dark-mode-enabled": isDarkModeEnabled ? "1" : "0",
20 | "animations-enabled": animationsEnabled ? "1" : "0",
21 | ]
22 | app.launch()
23 | return app
24 | }
25 |
26 | func wait(for seconds: TimeInterval) {
27 | Thread.sleep(forTimeInterval: seconds)
28 | }
29 | }
30 |
31 | extension XCUIApplication {
32 | /// Matches content in the left split group.
33 | var sidebar: XCUIElement {
34 | self.groups["Sidebar"]
35 | }
36 |
37 | /// Matches content in the right split group.
38 | var mainContent: XCUIElement {
39 | self.groups["MainContent"]
40 | }
41 |
42 | var chatWebView: XCUIElement {
43 | self.groups["ChatWebView"].webViews.firstMatch
44 | }
45 |
46 | var conversationInfo: XCUIElement {
47 | self.scrollViews["ConversationInfo"]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Prose/ProseUITests/RosterSelectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import Foundation
7 | import XCTest
8 |
9 | final class RosterSelectionTests: XCTestCase {
10 | override func setUp() {
11 | self.continueAfterFailure = false
12 | }
13 |
14 | func testSwitchesConversationWhenSelectingRosterItem() {
15 | let app = XCUIApplication.launching(testCase: "roster-selection")
16 |
17 | app.sidebar.cells
18 | .containing(.staticText, identifier: "Oya Karaböcek").element.tap()
19 |
20 | XCTAssertTrue(
21 | app.chatWebView.staticTexts["Hello from oya.karabocek"]
22 | .waitForExistence(timeout: 5)
23 | )
24 |
25 | // Check that the MessageField placeholder is displayed correctly
26 | XCTAssertTrue(app.mainContent.staticTexts["Message Oya Karaböcek"].exists)
27 |
28 | app.sidebar.cells
29 | .containing(.staticText, identifier: "Donna Reed").element.tap()
30 |
31 | XCTAssertTrue(
32 | app.chatWebView.staticTexts["Donna Reed"]
33 | .waitForExistence(timeout: 5)
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Prose/UITestHost/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Prose/UITestHost/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Prose/UITestHost/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/UITestHost/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Prose/UITestHost/UITestHost.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Prose/UITestHost/UITestHostApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // This file is part of prose-app-macos.
3 | // Copyright (c) 2023 Prose Foundation
4 | //
5 |
6 | import AppKit
7 | import SwiftUI
8 | import TestHostApp
9 |
10 | @main
11 | struct UITestHostApp: App {
12 | var body: some Scene {
13 | TestScene()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------