├── .github
├── FUNDING.md
└── workflows
│ ├── ios.yml
│ └── swiftlint.yml
├── .gitignore
├── .swift-format
├── .swiftlint.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Configs
└── GlobalConfig.xcconfig
├── LICENSE
├── Mio
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Mio_128.png
│ │ ├── Mio_128@2x.png
│ │ ├── Mio_16.png
│ │ ├── Mio_16@2x.png
│ │ ├── Mio_256.png
│ │ ├── Mio_256@2x.png
│ │ ├── Mio_32.png
│ │ ├── Mio_32@2x.png
│ │ ├── Mio_512.png
│ │ └── Mio_512@2x.png
│ ├── Badge
│ │ ├── Contents.json
│ │ └── Edited.imageset
│ │ │ ├── Contents.json
│ │ │ └── Edited.pdf
│ ├── Color
│ │ ├── Contents.json
│ │ └── borderedMessageBackground.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ └── Icon
│ │ ├── AddRoom.imageset
│ │ ├── AddRoom.pdf
│ │ └── Contents.json
│ │ ├── Arrow
│ │ ├── Contents.json
│ │ └── UpLeft.imageset
│ │ │ ├── Contents.json
│ │ │ └── UpLeft.pdf
│ │ ├── Contents.json
│ │ ├── Edit.imageset
│ │ ├── Contents.json
│ │ └── Edit.pdf
│ │ ├── Paperclip.imageset
│ │ ├── Contents.json
│ │ └── Paperclip.pdf
│ │ ├── Paperplane.imageset
│ │ ├── Contents.json
│ │ └── Paperplane.pdf
│ │ ├── Pencil.imageset
│ │ ├── Contents.json
│ │ └── Pencil.pdf
│ │ ├── Smiley.imageset
│ │ ├── Contents.json
│ │ └── Smiley.pdf
│ │ ├── Trash.imageset
│ │ ├── Contents.json
│ │ └── Trash.pdf
│ │ └── User.imageset
│ │ ├── Contents.json
│ │ └── User.pdf
├── Info.plist
├── InternetAccessPolicy.plist
├── Mio.entitlements
└── Preview Content
│ └── Preview Assets.xcassets
│ └── Contents.json
├── Nio.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ ├── Nio.xcscheme
│ ├── NioKit-iOS.xcscheme
│ └── NioKit-macOS.xcscheme
├── Nio
├── Assets.xcassets
│ ├── App Icons
│ │ ├── Contents.json
│ │ ├── Default.imageset
│ │ │ ├── Contents.json
│ │ │ ├── icon_60pt@2x.png
│ │ │ └── icon_60pt@3x.png
│ │ ├── Six Colors Dark.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Six Colors Dark@2x.png
│ │ │ └── Six Colors Dark@3x.png
│ │ ├── Six Colors Light.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Six Colors Light@2x.png
│ │ │ └── Six Colors Light@3x.png
│ │ └── Sketch.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Sketch@2x.png
│ │ │ └── Sketch@3x.png
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon.png
│ │ ├── icon_20pt.png
│ │ ├── icon_20pt@2x-1.png
│ │ ├── icon_20pt@2x.png
│ │ ├── icon_20pt@3x.png
│ │ ├── icon_29pt-1.png
│ │ ├── icon_29pt.png
│ │ ├── icon_29pt@2x-1.png
│ │ ├── icon_29pt@2x.png
│ │ ├── icon_29pt@3x.png
│ │ ├── icon_40pt.png
│ │ ├── icon_40pt@2x-1.png
│ │ ├── icon_40pt@2x.png
│ │ ├── icon_40pt@3x.png
│ │ ├── icon_60pt@2x.png
│ │ ├── icon_60pt@3x.png
│ │ ├── icon_76pt.png
│ │ ├── icon_76pt@2x.png
│ │ └── icon_83.5@2x.png
│ ├── Badge
│ │ ├── Contents.json
│ │ └── Edited.imageset
│ │ │ ├── Contents.json
│ │ │ └── Edited.pdf
│ ├── Color
│ │ ├── Contents.json
│ │ └── borderedMessageBackground.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ ├── Icon
│ │ ├── AddRoom.imageset
│ │ │ ├── AddRoom.pdf
│ │ │ └── Contents.json
│ │ ├── Arrow
│ │ │ ├── Contents.json
│ │ │ └── UpLeft.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── UpLeft.pdf
│ │ ├── Contents.json
│ │ ├── Edit.imageset
│ │ │ ├── Contents.json
│ │ │ └── Edit.pdf
│ │ ├── Paperclip.imageset
│ │ │ ├── Contents.json
│ │ │ └── Paperclip.pdf
│ │ ├── Paperplane.imageset
│ │ │ ├── Contents.json
│ │ │ └── Paperplane.pdf
│ │ ├── Pencil.imageset
│ │ │ ├── Contents.json
│ │ │ └── Pencil.pdf
│ │ ├── Smiley.imageset
│ │ │ ├── Contents.json
│ │ │ └── Smiley.pdf
│ │ ├── Trash.imageset
│ │ │ ├── Contents.json
│ │ │ └── Trash.pdf
│ │ └── User.imageset
│ │ │ ├── Contents.json
│ │ │ └── User.pdf
│ └── stub-morpheus.imageset
│ │ ├── Contents.json
│ │ └── ijUMdVWrjjkJBy4gCmcRdP-1200-80.jpg
├── Authentication
│ ├── LoadingView.swift
│ └── LoginView.swift
├── Conversations
│ ├── ContextMenu
│ │ ├── EventContextMenu.swift
│ │ ├── EventContextMenuModel.swift
│ │ └── ReactionPicker.swift
│ ├── Event Views
│ │ ├── BadgeView.swift
│ │ ├── EventContainerView.swift
│ │ ├── GenericEventView.swift
│ │ ├── MessageView
│ │ │ ├── BorderedMessageView.swift
│ │ │ ├── BorderlessMessageView.swift
│ │ │ ├── MediaEventView.swift
│ │ │ ├── MessageView.swift
│ │ │ ├── MessageViewModel.swift
│ │ │ └── Reactions
│ │ │ │ ├── GroupedReactionsView.swift
│ │ │ │ ├── ReactionGroupView.swift
│ │ │ │ └── ReactionsListItemView.swift
│ │ ├── RedactionEventView.swift
│ │ ├── RoomMemberEventView.swift
│ │ ├── RoomNameEventView.swift
│ │ ├── RoomPowerLevelsEventView.swift
│ │ └── RoomTopicEventView.swift
│ ├── MessageComposerView.swift
│ ├── RecentRoomsView.swift
│ ├── RoomListItemView.swift
│ ├── RoomView.swift
│ └── TypingIndicatorView.swift
├── Extensions
│ ├── Color+Named.swift
│ ├── Color+allAccent.swift
│ ├── ContentSizeCategory.swift
│ ├── EnvironmentValues.swift
│ ├── NSAttributedString+Extensions.swift
│ ├── PreviewProvider+Enumeration.swift
│ ├── String+Emoji.swift
│ └── String+Extensions.swift
├── Generated
│ ├── Assets.swift
│ └── Strings.swift
├── Info.plist
├── NewConversation
│ └── NewConversationView.swift
├── Nio.entitlements
├── NioApp.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Resources
│ └── Alternate Icons
│ │ ├── Six Colors Dark
│ │ ├── Six Colors Dark@2x.png
│ │ └── Six Colors Dark@3x.png
│ │ ├── Six Colors Light
│ │ ├── Six Colors Light@2x.png
│ │ └── Six Colors Light@3x.png
│ │ └── Sketch
│ │ ├── Sketch@2x.png
│ │ └── Sketch@3x.png
├── RootView.swift
├── SceneDelegate.swift
├── Settings
│ ├── AppIcon.swift
│ └── SettingsView.swift
├── Shapes
│ └── IndividuallyRoundedRectangle.swift
├── Shared Views
│ ├── AttributedText.swift
│ ├── ImagePicker.swift
│ ├── MarkdownText.swift
│ ├── MessageTextViewWrapper.swift
│ ├── MultilineTextField.swift
│ ├── ReverseList.swift
│ ├── SFSymbol.swift
│ └── UITextViewWrapper.swift
├── Supporting Files
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── ar.lproj
│ │ └── Localizable.strings
│ ├── bg.lproj
│ │ └── Localizable.strings
│ ├── de.lproj
│ │ └── Localizable.strings
│ ├── en.lproj
│ │ └── Localizable.strings
│ ├── es.lproj
│ │ └── Localizable.strings
│ ├── et.lproj
│ │ └── Localizable.strings
│ ├── eu.lproj
│ │ └── Localizable.strings
│ ├── fa.lproj
│ │ └── Localizable.strings
│ ├── fi.lproj
│ │ └── Localizable.strings
│ ├── fr.lproj
│ │ └── Localizable.strings
│ ├── fy.lproj
│ │ └── Localizable.strings
│ ├── hr.lproj
│ │ └── Localizable.strings
│ ├── hu.lproj
│ │ └── Localizable.strings
│ ├── id.lproj
│ │ └── Localizable.strings
│ ├── it.lproj
│ │ └── Localizable.strings
│ ├── ja.lproj
│ │ └── Localizable.strings
│ ├── ko.lproj
│ │ └── Localizable.strings
│ ├── lo.lproj
│ │ └── Localizable.strings
│ ├── lt.lproj
│ │ └── Localizable.strings
│ ├── nb.lproj
│ │ └── Localizable.strings
│ ├── nl.lproj
│ │ └── Localizable.strings
│ ├── nn.lproj
│ │ └── Localizable.strings
│ ├── pl.lproj
│ │ └── Localizable.strings
│ ├── pt-BR.lproj
│ │ └── Localizable.strings
│ ├── pt.lproj
│ │ └── Localizable.strings
│ ├── ru.lproj
│ │ └── Localizable.strings
│ ├── si.lproj
│ │ └── Localizable.strings
│ ├── sk.lproj
│ │ └── Localizable.strings
│ ├── sq.lproj
│ │ └── Localizable.strings
│ ├── sv.lproj
│ │ └── Localizable.strings
│ ├── ta.lproj
│ │ └── Localizable.strings
│ ├── tr.lproj
│ │ └── Localizable.strings
│ ├── uk.lproj
│ │ └── Localizable.strings
│ ├── vi.lproj
│ │ └── Localizable.strings
│ ├── zh-Hans.lproj
│ │ └── Localizable.strings
│ └── zh-Hant.lproj
│ │ └── Localizable.strings
└── Utility
│ ├── Formatter.swift
│ ├── GroupedEvents.swift
│ ├── GroupingIterator.swift
│ ├── MXURL.swift
│ ├── PeekableIterator.swift
│ └── TypedEvents.swift
├── NioKit
├── Configuration
│ └── Configuration.swift
├── Extensions
│ ├── MX+Identifiable.swift
│ ├── MXClient+Publisher.swift
│ ├── MXCredentials+Keychain.swift
│ ├── MXEvent+Extensions.swift
│ ├── UXKit.swift
│ └── UserDefaults.swift
├── Models
│ ├── Custom Events
│ │ ├── CustomEvent.swift
│ │ ├── EditEvent.swift
│ │ └── ReactionEvent.swift
│ ├── EventCollection.swift
│ ├── NIORoom.swift
│ ├── NIORoomSummary.swift
│ └── Reaction.swift
└── Session
│ └── AccountStore.swift
├── NioKitTests
├── Info.plist
└── NioKitTests.swift
├── NioShareExtension
├── GetURL.js
├── Info.plist
├── NioShareExtension.entitlements
├── ShareContentView.swift
└── ShareViewController.swift
├── NioTests
├── EventCollectionTests.swift
├── GroupingIteratorTests.swift
├── Info.plist
├── Mocks
│ └── MockEvent.swift
├── PeekableIteratorTests.swift
├── RoomMemberEventViewTests.swift
└── TypedEventsTests.swift
├── README.md
├── Resources
├── AppIcon
│ ├── Default
│ │ ├── Default.sketch
│ │ ├── Icon.png
│ │ ├── Rounded_1024.png
│ │ ├── Rounded_500.png
│ │ ├── icon_20pt.png
│ │ ├── icon_20pt@2x.png
│ │ ├── icon_20pt@3x.png
│ │ ├── icon_29pt.png
│ │ ├── icon_29pt@2x.png
│ │ ├── icon_29pt@3x.png
│ │ ├── icon_40pt.png
│ │ ├── icon_40pt@2x.png
│ │ ├── icon_40pt@3x.png
│ │ ├── icon_60pt@2x.png
│ │ ├── icon_60pt@3x.png
│ │ ├── icon_76pt.png
│ │ ├── icon_76pt@2x.png
│ │ └── icon_83.5@2x.png
│ ├── Six Colors Dark
│ │ ├── Six Colors Dark.png
│ │ ├── Six Colors Dark@2x.png
│ │ ├── Six Colors Dark@3x.png
│ │ └── Six Colors.sketch
│ ├── Six Colors Light
│ │ ├── Six Colors Light.png
│ │ ├── Six Colors Light@2x.png
│ │ └── Six Colors Light@3x.png
│ ├── Sketch
│ │ ├── Sketch.png
│ │ ├── Sketch@2x.png
│ │ └── Sketch@3x.png
│ └── attribution.txt
├── Application.sketch
└── Base
│ ├── Chat.pdf
│ ├── Chat.png
│ ├── Chat.sketch
│ ├── Chat.svg
│ └── attribution.txt
├── fastlane
├── Fastfile
└── README.md
└── swiftgen.yml
/.github/FUNDING.md:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # otechie: # Replace with a single Otechie username
12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
14 | liberapay: nio
15 |
--------------------------------------------------------------------------------
/.github/workflows/ios.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: stable
7 |
8 | jobs:
9 | build_and_test:
10 | runs-on: macos-latest
11 | steps:
12 | - uses: actions/checkout@v1
13 | - name: Version
14 | run: |
15 | sudo xcode-select --switch /Applications/Xcode_13.1.app
16 | xcode-select --print-path
17 | xcodebuild -version
18 | - name: Prerequisites
19 | run: |
20 | gem install xcpretty
21 | - name: Build
22 | run: |
23 | set -o pipefail
24 | xcodebuild -scheme Nio -destination 'platform=iOS Simulator,name=iPhone 12' build | xcpretty --color
25 | - name: Test
26 | run: |
27 | set -o pipefail
28 | xcodebuild -scheme Nio -destination 'platform=iOS Simulator,name=iPhone 12' test | xcpretty --color --test
29 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | swiftlint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v1
10 | - name: lint changed files
11 | uses: norio-nomura/action-swiftlint@3.1.0
12 | env:
13 | DIFF_BASE: ${{ github.base_ref }}
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*/xcuserdata
2 | Pods/CocoaPodsKeys
3 | fastlane/Appfile
4 | Configs/LocalConfig.xcconfig
5 |
--------------------------------------------------------------------------------
/.swift-format:
--------------------------------------------------------------------------------
1 | {
2 | "fileScopedDeclarationPrivacy" : {
3 | "accessLevel" : "private"
4 | },
5 | "indentation" : {
6 | "spaces" : 4
7 | },
8 | "indentConditionalCompilationBlocks" : true,
9 | "lineBreakAroundMultilineExpressionChainComponents" : false,
10 | "lineBreakBeforeControlFlowKeywords" : false,
11 | "lineBreakBeforeEachArgument" : false,
12 | "lineBreakBeforeEachGenericRequirement" : false,
13 | "lineLength" : 120,
14 | "maximumBlankLines" : 1,
15 | "prioritizeKeepingFunctionOutputTogether" : false,
16 | "respectsExistingLineBreaks" : true,
17 | "rules" : {
18 | "AllPublicDeclarationsHaveDocumentation" : false,
19 | "AlwaysUseLowerCamelCase" : false,
20 | "AmbiguousTrailingClosureOverload" : true,
21 | "BeginDocumentationCommentWithOneLineSummary" : true,
22 | "DoNotUseSemicolons" : true,
23 | "DontRepeatTypeInStaticProperties" : true,
24 | "FileprivateAtFileScope" : true,
25 | "FullyIndirectEnum" : true,
26 | "GroupNumericLiterals" : true,
27 | "IdentifiersMustBeASCII" : true,
28 | "NeverForceUnwrap" : false,
29 | "NeverUseForceTry" : true,
30 | "NeverUseImplicitlyUnwrappedOptionals" : true,
31 | "NoAccessLevelOnExtensionDeclaration" : true,
32 | "NoBlockComments" : true,
33 | "NoCasesWithOnlyFallthrough" : true,
34 | "NoEmptyTrailingClosureParentheses" : true,
35 | "NoLabelsInCasePatterns" : true,
36 | "NoLeadingUnderscores" : true,
37 | "NoParensAroundConditions" : true,
38 | "NoVoidReturnOnFunctionSignature" : true,
39 | "OneCasePerLine" : true,
40 | "OneVariableDeclarationPerLine" : true,
41 | "OnlyOneTrailingClosureArgument" : true,
42 | "OrderedImports" : true,
43 | "ReturnVoidInsteadOfEmptyTuple" : true,
44 | "UseLetInEveryBoundCaseVariable" : true,
45 | "UseShorthandTypeNames" : true,
46 | "UseSingleLinePropertyGetter" : true,
47 | "UseSynthesizedInitializer" : true,
48 | "UseTripleSlashForDocumentationComments" : true,
49 | "ValidateDocumentationComments" : true
50 | },
51 | "tabWidth" : 8,
52 | "version" : 1
53 | }
54 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_comma
3 | - empty_enum_arguments
4 | - void_return
5 | - redundant_optional_initialization
6 | - nesting
7 | - redundant_discardable_let
8 | - opening_brace
9 | - multiple_closures_with_trailing_closure
10 | excluded:
11 | - Pods
12 | line_length:
13 | warning: 140
14 | error: 140
15 | identifier_name:
16 | excluded:
17 | - 'id'
18 | - 'i'
19 | - 'x'
20 | - 'y'
21 | - 'z'
22 | type_name:
23 | max_length: 60
24 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at coc@nio.chat. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Nio
2 |
3 | First off, thanks for taking the time to contribute! 😍🥳
4 | There are several ways you can leave your mark, we're extremely glad to have you!
5 |
6 | ## Feedback
7 |
8 | At the current time, Nio is not yet ready for an actual release on the App Store. Many essential features are [still
9 | missing](https://github.com/niochat/nio/issues?q=is%3Aopen+is%3Aissue+label%3Aessential). Current builds are distributed via
10 | [TestFlight](https://testflight.apple.com/join/KlXr3kKz). We heavily rely on people installing these builds and telling us
11 | if other things are not yet working as expected or if there are bugs in the existing features.
12 |
13 | Should you come across anything in that regard, be it as simple as a typo in the app's description, behavior that doesn't feel
14 | correct, a crash of the application or *anything* else, please reach out. Either by [opening an
15 | issue](https://github.com/niochat/nio/issues/new) directly on GitHub, sending a message in our Matrix room
16 | [#niochat:matrix.org](https://matrix.to/#/#niochat:matrix.org) or via mail to
17 | [team@nio.chat](mailto:team@nio.chat).
18 |
19 | Please also feel free to comment on existing issues and pull requests. A new opinion, viewpoint or even just an upvote is
20 | always good to have.
21 |
22 | We know we're still far away from the goal, but Nio is aiming to be as inclusive as possible and while trying to do our best,
23 | we also rely on people like you telling us if things are anywhere between unclear and confusing or just downright wrong.
24 |
25 | Thank you! ❤️
26 |
27 | ## Localization
28 |
29 | As Nio aims to be accessible by anyone from around the globe, it's important to have Nio speak as many languages as possible.
30 | Nio uses a tool called Weblate running on [translate.riot.im](https://translate.riot.im/engage/nio) to manage
31 | translation files. Unfortunately you do need an account there, but it's of course free and easy to register for one. After
32 | doing that you can fix and modify existing translations or add new languages altogether, both of which are *highly*
33 | appreciated!
34 |
35 | If Weblate isn't to your liking and you prefer a text editor and git, feel free to edit the translations directly in the
36 | project's repository. They're synchronized both ways.
37 |
38 | This is the current status of the translations:
39 |
40 | [](https://translate.riot.im/engage/nio/?utm_source=widget)
41 |
42 | ## Custom App Icons
43 |
44 | Nio includes a few basic app icons, but a bigger selection of community icons would be fantastic. All that's necessary to include a new icon is a 1024x1024px PNG file. Please don't include any rounded edges, iOS does that automatically. Check the [Resources](https://github.com/niochat/nio/tree/stable/Resources) directory for the existing icons and the base chat bubbles in different formats if you want to build on that. You can send the finished file to our Matrix room or mail address (all listed on [nio.chat](https://nio.chat)) or just [open a new issue](https://github.com/niochat/nio/issues/new) on GitHub 😊
45 |
46 | ## Implementation
47 |
48 | Nio has (and probably always will have 😅) a [long list of open issues](https://github.com/niochat/nio/issues). You are more
49 | than *extremely* welcome to tackle any of these and send pull requests.
50 |
51 | ### Dependencies
52 | Nio manages its dependencies via Swift package manager, which Xcode handles automatically. The `matrix-ios-sdk`
53 | however doesn't directly support SwiftPM, so we build our own compatible framework and pull it from [niochat/MatrixSDK](https://github.com/niochat/MatrixSDK).
54 |
55 | ### Automatic Signing
56 | In order to use automatic signing in Xcode, it is possible to create
57 | `Configs/LocalConfig.xcconfig` and override the default values. For more info
58 | have a look [here](https://www.matrixprojects.net/p/xcconfig-for-shared-projects/).
59 |
60 | If you are unsure where to start or have other questions, please reach out, preferably via Matrix in
61 | [#niochat:matrix.org](https://matrix.to/#/#niochat:matrix.org) or whatever works for you 😊
62 |
--------------------------------------------------------------------------------
/Configs/GlobalConfig.xcconfig:
--------------------------------------------------------------------------------
1 | // Configuration settings file format documentation can be found at:
2 | // https://help.apple.com/xcode/#/dev745c5c974
3 |
4 | // Nio: When working on your fork of Nio, create the `LocalConfig.xcconfig`
5 | // file, and set the `NIO_NAMESPACE` to some reverse DNS you own.
6 | // The same goes for the `DEVELOPMENT_TEAM`.
7 | // Example `LocalConfig.xcconfig`:
8 | // NIO_NAMESPACE = com.example.nio
9 | // DEVELOPMENT_TEAM = Z123456789
10 |
11 | NIO_NAMESPACE = com.example.nio
12 | DEVELOPMENT_TEAM = Z123456789
13 |
14 | APPGROUP = $(NIO_NAMESPACE)
15 | PRODUCT_BUNDLE_IDENTIFIER = $(NIO_NAMESPACE).iOS
16 | MAC_PRODUCT_BUNDLE_IDENTIFIER = $(NIO_NAMESPACE).mio
17 | SHARE_EXTENSION_PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).NioShareExtension
18 | CODE_SIGN_STYLE = Manual
19 |
20 | #include? "LocalConfig.xcconfig"
21 |
--------------------------------------------------------------------------------
/Mio/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 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Mio_16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "Mio_16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "Mio_32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "Mio_32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "Mio_128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "Mio_128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "Mio_256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "Mio_256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "Mio_512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "Mio_512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_128.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_128@2x.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_16.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_16@2x.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_256.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_256@2x.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_32.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_32@2x.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_512.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/AppIcon.appiconset/Mio_512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/AppIcon.appiconset/Mio_512@2x.png
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Badge/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Badge/Edited.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Edited.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Badge/Edited.imageset/Edited.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Badge/Edited.imageset/Edited.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Color/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Color/borderedMessageBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.900",
9 | "green" : "0.864",
10 | "red" : "0.855"
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.250",
27 | "green" : "0.226",
28 | "red" : "0.220"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/AddRoom.imageset/AddRoom.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/AddRoom.imageset/AddRoom.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/AddRoom.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AddRoom.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Arrow/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UpLeft.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/UpLeft.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/UpLeft.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Edit.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Edit.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Edit.imageset/Edit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Edit.imageset/Edit.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Paperclip.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Paperclip.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Paperclip.imageset/Paperclip.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Paperclip.imageset/Paperclip.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Paperplane.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Paperplane.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Paperplane.imageset/Paperplane.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Paperplane.imageset/Paperplane.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Pencil.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Pencil.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Pencil.imageset/Pencil.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Pencil.imageset/Pencil.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Smiley.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Smiley.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Smiley.imageset/Smiley.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Smiley.imageset/Smiley.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Trash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Trash.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/Trash.imageset/Trash.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/Trash.imageset/Trash.pdf
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/User.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "User.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Mio/Assets.xcassets/Icon/User.imageset/User.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Mio/Assets.xcassets/Icon/User.imageset/User.pdf
--------------------------------------------------------------------------------
/Mio/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSMinimumSystemVersion
22 | $(MACOSX_DEPLOYMENT_TARGET)
23 | NSHumanReadableCopyright
24 | Copyright © 2021 Kilian Koeltzsch. All rights reserved.
25 |
26 | AppIdentifierPrefix
27 | $(AppIdentifierPrefix)
28 | TeamIdentifierPrefix
29 | $(TeamIdentifierPrefix)
30 | BugReporterURL
31 | https://github.com/niochat/nio/issues
32 |
33 | NSAppTransportSecurity
34 |
35 | NSAllowsArbitraryLoads
36 |
37 |
38 |
39 | AppGroup
40 | $(APPGROUP)
41 | DevelopmentTeam
42 | $(DEVELOPMENT_TEAM)
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Mio/InternetAccessPolicy.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ApplicationDescription
6 | Mio is a client for the Matrix messaging service.
7 | Connections
8 |
9 |
10 | IsIncoming
11 |
12 | Host
13 | *
14 | Port
15 | 443
16 | NetworkProtocol
17 | TCP
18 | Relevance
19 | Essential
20 | Purpose
21 | Mio needs the connection to contact Matrix services.
22 | DenyConsequences
23 | If you deny this connection, Mio won't be able to send or receive messages.
24 |
25 |
26 | IsIncoming
27 |
28 | Host
29 | *
30 | Port
31 | 80
32 | NetworkProtocol
33 | TCP
34 | Relevance
35 | default
36 | Purpose
37 | Mio needs the connections to retrieve arbitrary web resources referred to by users or bots in Matrix messages.
38 | DenyConsequences
39 | If you deny this connection, Mio won't be able to receive assets from the web for inline display.
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Mio/Mio.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-write
8 |
9 | com.apple.security.network.client
10 |
11 | com.apple.security.personal-information.addressbook
12 |
13 | com.apple.security.personal-information.photos-library
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Mio/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Nio.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Nio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Nio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "BlurHash",
6 | "repositoryURL": "https://github.com/niochat/BlurHash",
7 | "state": {
8 | "branch": null,
9 | "revision": "1c54f004fa1314ebcf678fad33381afb449a79b9",
10 | "version": "0.1.0"
11 | }
12 | },
13 | {
14 | "package": "CommonMark",
15 | "repositoryURL": "https://github.com/SwiftDocOrg/CommonMark.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "35718fc895ff25908628a47b44ae74aba75b6770",
19 | "version": "0.3.2"
20 | }
21 | },
22 | {
23 | "package": "CommonMarkAttributedString",
24 | "repositoryURL": "https://github.com/mattt/CommonMarkAttributedString",
25 | "state": {
26 | "branch": null,
27 | "revision": "578419f521d16fd5fb6e757e7f5a6a4748fc0c38",
28 | "version": "0.2.0"
29 | }
30 | },
31 | {
32 | "package": "KeychainAccess",
33 | "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess",
34 | "state": {
35 | "branch": null,
36 | "revision": "654d52d30f3dd4592e944c3e0bccb53178c992f6",
37 | "version": "4.2.1"
38 | }
39 | },
40 | {
41 | "package": "MatrixSDK",
42 | "repositoryURL": "https://github.com/niochat/MatrixSDK",
43 | "state": {
44 | "branch": null,
45 | "revision": "faab7d8d55910f40589111caf9392d67c24889a9",
46 | "version": "0.20.14"
47 | }
48 | },
49 | {
50 | "package": "SDWebImage",
51 | "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "e857b78ca4c8afe616b794aeb090d40f4e348072",
55 | "version": "5.10.0"
56 | }
57 | },
58 | {
59 | "package": "SDWebImageSwiftUI",
60 | "repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI.git",
61 | "state": {
62 | "branch": null,
63 | "revision": "4c7f169e39bc35d6b80d42b8eb8301bee9cd0907",
64 | "version": "1.5.0"
65 | }
66 | },
67 | {
68 | "package": "cmark",
69 | "repositoryURL": "https://github.com/SwiftDocOrg/swift-cmark.git",
70 | "state": {
71 | "branch": null,
72 | "revision": "1168665f6b36be747ffe6b7b90bc54cfc17f42b7",
73 | "version": "0.28.3+20200207.1168665"
74 | }
75 | },
76 | {
77 | "package": "Introspect",
78 | "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
79 | "state": {
80 | "branch": null,
81 | "revision": "36ecf80429d00a4cd1e81fbfe4655b1d99ebd651",
82 | "version": "0.1.2"
83 | }
84 | },
85 | {
86 | "package": "SwiftyBeaver",
87 | "repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver",
88 | "state": {
89 | "branch": null,
90 | "revision": "2c039501d6eeb4d4cd4aec4a8d884ad28862e044",
91 | "version": "1.9.5"
92 | }
93 | }
94 | ]
95 | },
96 | "version": 1
97 | }
98 |
--------------------------------------------------------------------------------
/Nio.xcodeproj/xcshareddata/xcschemes/NioKit-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Nio.xcodeproj/xcshareddata/xcschemes/NioKit-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Default.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon_60pt@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "icon_60pt@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "original"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Default.imageset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Default.imageset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Default.imageset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Default.imageset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Dark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Six Colors Dark@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "Six Colors Dark@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "original"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Dark.imageset/Six Colors Dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Six Colors Dark.imageset/Six Colors Dark@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Dark.imageset/Six Colors Dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Six Colors Dark.imageset/Six Colors Dark@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Light.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Six Colors Light@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "Six Colors Light@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "original"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Light.imageset/Six Colors Light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Six Colors Light.imageset/Six Colors Light@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Six Colors Light.imageset/Six Colors Light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Six Colors Light.imageset/Six Colors Light@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Sketch.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Sketch@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "Sketch@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "original"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Sketch.imageset/Sketch@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Sketch.imageset/Sketch@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/App Icons/Sketch.imageset/Sketch@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/App Icons/Sketch.imageset/Sketch@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "icon_20pt@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "icon_20pt@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "icon_29pt.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "icon_29pt@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "icon_29pt@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "icon_40pt@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "icon_40pt@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "icon_60pt@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "icon_60pt@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "icon_20pt.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "icon_20pt@2x-1.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "icon_29pt-1.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "icon_29pt@2x-1.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "icon_40pt.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "icon_40pt@2x-1.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "icon_76pt.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "icon_76pt@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "icon_83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt-1.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Badge/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Badge/Edited.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Edited.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Badge/Edited.imageset/Edited.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Badge/Edited.imageset/Edited.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Color/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Color/borderedMessageBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0.855",
13 | "alpha" : "1.000",
14 | "blue" : "0.900",
15 | "green" : "0.864"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.220",
31 | "alpha" : "1.000",
32 | "blue" : "0.250",
33 | "green" : "0.226"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/AddRoom.imageset/AddRoom.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/AddRoom.imageset/AddRoom.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/AddRoom.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AddRoom.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Arrow/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UpLeft.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/UpLeft.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Arrow/UpLeft.imageset/UpLeft.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Edit.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Edit.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Edit.imageset/Edit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Edit.imageset/Edit.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Paperclip.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Paperclip.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Paperclip.imageset/Paperclip.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Paperclip.imageset/Paperclip.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Paperplane.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Paperplane.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Paperplane.imageset/Paperplane.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Paperplane.imageset/Paperplane.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Pencil.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Pencil.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Pencil.imageset/Pencil.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Pencil.imageset/Pencil.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Smiley.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Smiley.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Smiley.imageset/Smiley.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Smiley.imageset/Smiley.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Trash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Trash.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/Trash.imageset/Trash.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/Trash.imageset/Trash.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/User.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "User.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/Icon/User.imageset/User.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/Icon/User.imageset/User.pdf
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/stub-morpheus.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ijUMdVWrjjkJBy4gCmcRdP-1200-80.jpg"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Nio/Assets.xcassets/stub-morpheus.imageset/ijUMdVWrjjkJBy4gCmcRdP-1200-80.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Assets.xcassets/stub-morpheus.imageset/ijUMdVWrjjkJBy4gCmcRdP-1200-80.jpg
--------------------------------------------------------------------------------
/Nio/Authentication/LoadingView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | import NioKit
4 |
5 | struct LoadingView: View {
6 | @EnvironmentObject private var store: AccountStore
7 |
8 | private let loadingEmoji = [
9 | "🧑🎤",
10 | "🧑🏭",
11 | "🧑🔧",
12 | "🧑💻",
13 | ]
14 |
15 | private let loadingMessages = [
16 | L10n.Loading._1,
17 | L10n.Loading._2,
18 | L10n.Loading._3,
19 | L10n.Loading._4,
20 | ]
21 |
22 | private var randomLoadingMessage: String {
23 | "\(loadingEmoji.randomElement()!) \(loadingMessages.randomElement()!)"
24 | }
25 |
26 | var body: some View {
27 | VStack {
28 | Spacer()
29 |
30 | ProgressView().padding(1)
31 |
32 | Text(self.randomLoadingMessage)
33 | .bold()
34 | .padding(.horizontal)
35 |
36 | Spacer()
37 |
38 | Button(action: self.store.logout) {
39 | Text(verbatim: L10n.Loading.cancel).font(.callout)
40 | }
41 | .padding()
42 | }
43 | }
44 | }
45 |
46 | struct LoadingView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | LoadingView()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Nio/Conversations/ContextMenu/EventContextMenu.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import MatrixSDK
3 |
4 | private struct EventContextMenuViewModel {
5 | fileprivate let canReact: Bool
6 | fileprivate let canReply: Bool
7 | fileprivate let canEdit: Bool
8 | fileprivate let canRedact: Bool
9 |
10 | init(event: MXEvent, userId: String) {
11 | var canReact = false
12 | let canReply = false
13 | var canEdit = false
14 | var canRedact = false
15 |
16 | // The correct way to check if replying is possible is `-[MXRoom canReplyToEvent:]`.
17 |
18 | let reactableEvents = [
19 | kMXEventTypeStringRoomMessage
20 | ]
21 |
22 | if reactableEvents.contains(event.type ?? "") {
23 | canReact = true
24 | // canReply = true
25 | }
26 |
27 | // TODO: Redacting messages is a powerlevel thing, you can't only redact your own.
28 | if event.sender == userId
29 | && reactableEvents.contains(event.type ?? "")
30 | && !event.isRedactedEvent() {
31 | canEdit = true
32 | canRedact = true
33 | }
34 |
35 | if event.isMediaAttachment() {
36 | canEdit = false
37 | }
38 |
39 | self.canReact = canReact
40 | self.canReply = canReply
41 | self.canEdit = canEdit
42 | self.canRedact = canRedact
43 | }
44 | }
45 |
46 | struct EventContextMenu: View {
47 | private var model: EventContextMenuViewModel
48 |
49 | typealias Action = () -> Void
50 |
51 | private let onReact: Action
52 | private let onReply: Action
53 | private let onEdit: Action
54 | private let onRedact: Action
55 |
56 | init(model: EventContextMenuModel) {
57 | self.init(event: model.event,
58 | userId: model.userId,
59 | onReact: model.onReact,
60 | onReply: model.onReply,
61 | onEdit: model.onEdit,
62 | onRedact: model.onRedact)
63 | }
64 |
65 | init(event: MXEvent,
66 | userId: String,
67 | onReact: @escaping Action,
68 | onReply: @escaping Action,
69 | onEdit: @escaping Action,
70 | onRedact: @escaping Action
71 | ) {
72 | self.model = EventContextMenuViewModel(event: event, userId: userId)
73 | self.onReact = onReact
74 | self.onReply = onReply
75 | self.onEdit = onEdit
76 | self.onRedact = onRedact
77 | }
78 |
79 | var body: some View {
80 | Group {
81 | if model.canReact {
82 | Button(action: onReact, label: {
83 | Text(verbatim: L10n.Event.ContextMenu.addReaction)
84 | Image(Asset.Icon.smiley.name)
85 | .resizable()
86 | .frame(width: 30.0, height: 30.0)
87 | })
88 | }
89 | if model.canReply {
90 | Button(action: onReply, label: {
91 | Text(verbatim: L10n.Event.ContextMenu.reply)
92 | Image(Asset.Icon.Arrow.upLeft.name)
93 | .resizable()
94 | .frame(width: 30.0, height: 30.0)
95 | })
96 | }
97 | if model.canEdit {
98 | Button(action: onEdit, label: {
99 | Text(verbatim: L10n.Event.ContextMenu.edit)
100 | Image(Asset.Icon.pencil.name)
101 | .resizable()
102 | .frame(width: 30.0, height: 30.0)
103 | })
104 | }
105 | if model.canRedact {
106 | Button(action: onRedact, label: {
107 | Text(verbatim: L10n.Event.ContextMenu.remove)
108 | Image(Asset.Icon.trash.name)
109 | .resizable()
110 | .frame(width: 30.0, height: 30.0)
111 | })
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Nio/Conversations/ContextMenu/EventContextMenuModel.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import MatrixSDK
3 |
4 | struct EventContextMenuModel {
5 | typealias Action = () -> Void
6 |
7 | var event: MXEvent
8 | var userId: String
9 |
10 | var onReact: Action
11 | var onReply: Action
12 | var onEdit: Action
13 | var onRedact: Action
14 | }
15 |
16 | extension EventContextMenuModel {
17 | static var previewModel: EventContextMenuModel {
18 | EventContextMenuModel(event: MXEvent(), userId: "Jane", onReact: {}, onReply: {}, onEdit: {}, onRedact: {})
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Nio/Conversations/ContextMenu/ReactionPicker.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct ReactionPicker: View {
4 | let emoji = ["👍", "👎", "😄", "🎉", "❤️", "🚀", "👀"]
5 |
6 | let picked: (String) -> Void
7 |
8 | var body: some View {
9 | VStack {
10 | Text(verbatim: L10n.ReactionPicker.title)
11 | .foregroundColor(.gray)
12 | .font(.headline)
13 | .padding(.bottom, 30)
14 | HStack(spacing: 10) {
15 | ForEach(emoji, id: \.self) { emoji in
16 | Button(action: { self.picked(emoji) },
17 | label: {
18 | Text(emoji)
19 | .font(.largeTitle)
20 | })
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | struct ReactionPicker_Previews: PreviewProvider {
28 | static var previews: some View {
29 | ReactionPicker(picked: { _ in })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/BadgeView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | struct BadgeView: View {
5 | let image: Image
6 | let foregroundColor: Color
7 | let backgroundColor: Color
8 |
9 | var body: some View {
10 | let lineWidth: CGFloat = 3.0
11 |
12 | let circle = Circle()
13 | .stroke(foregroundColor, lineWidth: lineWidth)
14 | .overlay(
15 | Circle()
16 | .fill(backgroundColor)
17 | )
18 | .padding(lineWidth)
19 |
20 | return image
21 | .resizable()
22 | .aspectRatio(contentMode: .fit)
23 | .foregroundColor(foregroundColor)
24 | .padding(4.0)
25 | .background(circle)
26 | }
27 | }
28 |
29 | struct BadgeView_Previews: PreviewProvider {
30 | static var previews: some View {
31 | let image = Image(Asset.Badge.edited.name)
32 | let foregroundColor = Color(UXColor.lightGray)
33 | let backgroundColor = Color(UXColor.darkGray)
34 |
35 | return BadgeView(
36 | image: image,
37 | foregroundColor: foregroundColor,
38 | backgroundColor: backgroundColor
39 | )
40 | .previewLayout(.sizeThatFits)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/GenericEventView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import SDWebImageSwiftUI
3 |
4 | import NioKit
5 |
6 | struct GenericEventView: View {
7 | @EnvironmentObject private var store: AccountStore
8 |
9 | let text: String
10 | var image: MXURL?
11 | private var imageURL: URL? {
12 | return store.client?.homeserver
13 | .flatMap(URL.init(string:))
14 | .flatMap { image?.contentURL(on: $0) }
15 | }
16 |
17 | var body: some View {
18 | HStack(spacing: 4) {
19 | Spacer()
20 | if imageURL != nil {
21 | WebImage(url: imageURL!)
22 | .resizable()
23 | .aspectRatio(contentMode: .fill)
24 | .frame(width: 15, height: 15)
25 | .mask(Circle())
26 | }
27 | Text(text)
28 | .font(.caption)
29 | .foregroundColor(.gray)
30 | Spacer()
31 | }
32 | .padding(.vertical, 3)
33 | }
34 | }
35 |
36 | struct GenericEventView_Previews: PreviewProvider {
37 | static var previews: some View {
38 | Group {
39 | GenericEventView(text: "Ping joined", image: .nioIcon)
40 | GenericEventView(text: "Ping changed the topic to '🐧'")
41 |
42 | VStack(spacing: 0) {
43 | GenericEventView(text: "Ping joined")
44 | GenericEventView(text: "Ping joined")
45 | GenericEventView(text: "Ping joined")
46 | }
47 | }
48 | .accentColor(.purple)
49 | // .environment(\.colorScheme, .dark)
50 | .padding()
51 | .previewLayout(.sizeThatFits)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/MessageView/MessageView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import MatrixSDK
3 |
4 | import NioKit
5 |
6 | struct MessageView: View where Model: MessageViewModelProtocol {
7 | @Environment(\.colorScheme) private var colorScheme
8 | @Environment(\.sizeCategory) private var sizeCategory: ContentSizeCategory
9 | @Environment(\.userId) private var userId
10 |
11 | @Binding var model: Model
12 | let contextMenuModel: EventContextMenuModel
13 | let connectedEdges: ConnectedEdges
14 | var isEdited = false
15 |
16 | private var isMe: Bool {
17 | model.sender == userId
18 | }
19 |
20 | var body: some View {
21 | if model.isEmoji {
22 | let messageView = BorderlessMessageView(
23 | model: model,
24 | contextMenuModel: contextMenuModel,
25 | connectedEdges: connectedEdges,
26 | isEdited: self.isEdited
27 | )
28 | if isMe {
29 | HStack {
30 | Spacer()
31 | messageView
32 | }
33 | } else {
34 | HStack {
35 | messageView
36 | Spacer()
37 | }
38 | }
39 | } else {
40 | let messageView = BorderedMessageView(
41 | model: model,
42 | contextMenuModel: contextMenuModel,
43 | connectedEdges: connectedEdges,
44 | isEdited: self.isEdited
45 | )
46 | if isMe {
47 | HStack {
48 | Spacer()
49 | messageView
50 | }
51 | } else {
52 | HStack {
53 | messageView
54 | Spacer()
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
61 | struct MessageView_Previews: PreviewProvider {
62 | private struct MessageViewModel: MessageViewModelProtocol {
63 | var id: String
64 | var text: String
65 | var sender: String
66 | var sentState: MXEventSentState
67 | var showSender: Bool
68 | var timestamp: String
69 | var reactions: [Reaction]
70 | }
71 |
72 | static func message(text: String,
73 | sender: String,
74 | userId: String,
75 | showSender: Bool,
76 | reactions: [Reaction]
77 | ) -> some View {
78 | MessageView(
79 | model: .constant(MessageViewModel(
80 | id: "0",
81 | text: text,
82 | sender: sender,
83 | sentState: MXEventSentStateSent,
84 | showSender: showSender,
85 | timestamp: "12:29",
86 | reactions: reactions
87 | )),
88 | contextMenuModel: .previewModel,
89 | connectedEdges: []
90 | )
91 | .padding()
92 | .environment(\.userId, userId)
93 | }
94 |
95 | static var previews: some View {
96 | Group {
97 | message(
98 | text: "Hi there!",
99 | sender: "John Doe",
100 | userId: "Jane Doe",
101 | showSender: true,
102 | reactions: []
103 | )
104 |
105 | message(
106 | text: "👋",
107 | sender: "John Doe",
108 | userId: "Jane Doe",
109 | showSender: false,
110 | reactions: []
111 | )
112 | }
113 | .accentColor(.purple)
114 | .previewLayout(.sizeThatFits)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/MessageView/MessageViewModel.swift:
--------------------------------------------------------------------------------
1 | import MatrixSDK
2 | import NioKit
3 |
4 | protocol MessageViewModelProtocol {
5 | var id: String { get }
6 | var text: String { get }
7 | var sender: String { get }
8 | var sentState: MXEventSentState { get }
9 | var showSender: Bool { get }
10 | var timestamp: String { get }
11 |
12 | var reactions: [Reaction] { get }
13 | }
14 |
15 | extension MessageViewModelProtocol {
16 | var isEmoji: Bool {
17 | (text.count <= 3) && text.containsOnlyEmoji
18 | }
19 | }
20 |
21 | struct MessageViewModel: MessageViewModelProtocol {
22 | enum Error: Swift.Error {
23 | case invalidEventType(MXEventType)
24 |
25 | var localizedDescription: String {
26 | switch self {
27 | case .invalidEventType(let type):
28 | return "Expected message event, found \(type)"
29 | }
30 | }
31 | }
32 |
33 | var id: String {
34 | event.eventId
35 | }
36 |
37 | var text: String {
38 | if !event.isEdit() {
39 | return (event.content["body"] as? String).map {
40 | $0.trimmingCharacters(in: .whitespacesAndNewlines)
41 | } ?? "Error: expected string body"
42 | } else {
43 | let newContent = event.content["m.new_content"]! as? NSDictionary
44 | return (newContent?["body"] as? String).map {
45 | $0.trimmingCharacters(in: .whitespacesAndNewlines)
46 | } ?? "Error: expected string body"
47 | }
48 | }
49 |
50 | var sender: String {
51 | event.sender
52 | }
53 |
54 | var sentState: MXEventSentState {
55 | event.sentState
56 | }
57 |
58 | var showSender: Bool
59 |
60 | var timestamp: String {
61 | Formatter.string(for: event.timestamp, timeStyle: .short)
62 | }
63 |
64 | var reactions: [Reaction]
65 |
66 | private let event: MXEvent
67 |
68 | public init(event: MXEvent, reactions: [Reaction], showSender: Bool) throws {
69 | try Self.validate(event: event)
70 |
71 | self.event = event
72 | self.reactions = reactions
73 | self.showSender = showSender
74 | }
75 |
76 | private static func validate(event: MXEvent) throws {
77 | // NOTE: For as long as https://github.com/matrix-org/matrix-ios-sdk/pull/843
78 | // remains unresolved keep in mind that
79 | // `.keyVerificationStart`, `.keyVerificationAccept`, `.keyVerificationKey`,
80 | // `.keyVerificationMac`, `.keyVerificationCancel` & `.reaction`
81 | // may get wrongly recognized as `.custom(…)`, instead.
82 | // FIXME: Remove comment when linked bug fix has been merged.
83 | let eventType = MXEventType(identifier: event.type)
84 | guard eventType == .roomMessage else {
85 | throw Error.invalidEventType(eventType)
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/MessageView/Reactions/ReactionGroupView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct ReactionGroupView: View {
4 | private let text: String
5 | private let count: Int
6 |
7 | private let backgroundColor: Color
8 |
9 | init(text: String, count: Int, backgroundColor: Color) {
10 | assert(count > 0, "Expected non-zero positive integer")
11 |
12 | self.text = text
13 | self.count = count
14 | self.backgroundColor = backgroundColor
15 | }
16 |
17 | var body: some View {
18 | return HStack(spacing: 1) {
19 | Text(self.text)
20 | Text(String(self.count))
21 | }
22 | .font(.footnote)
23 | .padding(EdgeInsets(top: 4.0, leading: 8.0, bottom: 4.0, trailing: 8.0))
24 | .background(
25 | RoundedRectangle(cornerRadius: 30)
26 | .fill(self.backgroundColor)
27 | )
28 | }
29 | }
30 |
31 | struct ReactionGroupView_Previews: PreviewProvider {
32 | static var reactionView: some View {
33 | ReactionGroupView(
34 | text: "💩",
35 | count: 42,
36 | backgroundColor: Color.borderedMessageBackground
37 | )
38 | }
39 |
40 | static var previews: some View {
41 | Group {
42 | enumeratingColorSchemes {
43 | reactionView
44 | .padding()
45 | .previewLayout(.sizeThatFits)
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/MessageView/Reactions/ReactionsListItemView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | import NioKit
4 |
5 | struct ReactionsListItemView: View {
6 | @Environment(\.colorScheme) private var colorScheme: ColorScheme
7 | @Environment(\.colorSchemeContrast) private var colorSchemeContrast: ColorSchemeContrast
8 | @Environment(\.userId) private var userId
9 |
10 | let reaction: Reaction
11 |
12 | private var timestamp: String {
13 | Formatter.string(
14 | for: self.reaction.timestamp,
15 | dateStyle: .short,
16 | timeStyle: .short
17 | )
18 | }
19 |
20 | var body: some View {
21 | HStack {
22 | Text(self.reaction.reaction)
23 | Text(self.reaction.sender)
24 | Spacer()
25 | Text(timestamp)
26 | .font(.footnote)
27 | }
28 | }
29 | }
30 |
31 | struct ReactionsListItemView_Previews: PreviewProvider {
32 | static var previews: some View {
33 | Group {
34 | enumeratingColorSchemes {
35 | ReactionsListItemView(
36 | reaction: Reaction(
37 | id: "0",
38 | sender: "Jane Doe",
39 | timestamp: Date(),
40 | reaction: "❤️"
41 | )
42 | )
43 | .padding()
44 | }
45 | }
46 | .previewLayout(.sizeThatFits)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/RedactionEventView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct RedactionEventView: View {
4 | struct ViewModel {
5 | let sender: String
6 | let redactor: String
7 | let reason: String?
8 | }
9 |
10 | let model: ViewModel
11 |
12 | private var redactionText: String {
13 | if model.sender == model.redactor {
14 | return "🗑 \(L10n.Event.Redaction.redactSelf(model.redactor))"
15 | }
16 | return "🗑 \(L10n.Event.Redaction.redactOther(model.redactor, model.sender))"
17 | }
18 |
19 | var body: some View {
20 | HStack {
21 | Spacer()
22 | VStack {
23 | Text(redactionText)
24 | .font(.caption)
25 | .foregroundColor(.gray)
26 | if model.reason != nil {
27 | Text(verbatim: L10n.Event.reason(model.reason!))
28 | .foregroundColor(.gray)
29 | .font(.callout)
30 | }
31 | }
32 | Spacer()
33 | }
34 | .padding(.vertical, 3)
35 | }
36 | }
37 |
38 | struct RedactionView_Previews: PreviewProvider {
39 | static var previews: some View {
40 | Group {
41 | RedactionEventView(model: .init(sender: "Jane Doe",
42 | redactor: "Jane Doe",
43 | reason: nil))
44 | .previewDisplayName("self redact")
45 | RedactionEventView(model: .init(sender: "John Doe",
46 | redactor: "Jane Doe",
47 | reason: nil))
48 | .previewDisplayName("redact other")
49 | RedactionEventView(model: .init(sender: "Jane Doe",
50 | redactor: "Jane Doe",
51 | reason: "Totally valid reason with longer text"))
52 | .previewDisplayName("self redact with reason")
53 |
54 | VStack(spacing: 0) {
55 | RedactionEventView(model: .init(sender: "Jane Doe",
56 | redactor: "Jane Doe",
57 | reason: nil))
58 | RedactionEventView(model: .init(sender: "Jane Doe",
59 | redactor: "Jane Doe",
60 | reason: "some reason"))
61 | RedactionEventView(model: .init(sender: "John Doe",
62 | redactor: "Jane Doe",
63 | reason: "spam spam spam"))
64 | }
65 | .previewDisplayName("spacing")
66 | }
67 | .padding()
68 | .previewLayout(.sizeThatFits)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/RoomNameEventView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import class MatrixSDK.MXEvent
3 |
4 | struct RoomNameEventView: View {
5 | struct ViewModel {
6 | fileprivate let sender: String
7 | fileprivate let newName: String
8 | fileprivate let oldName: String?
9 |
10 | init(event: MXEvent) {
11 | self.sender = event.sender ?? ""
12 | self.newName = event.content(valueFor: "name") ?? L10n.Event.unknownRoomNameFallback
13 | self.oldName = event.prevContent(valueFor: "name")
14 | }
15 |
16 | init(sender: String, newName: String, oldName: String?) {
17 | self.sender = sender
18 | self.newName = newName
19 | self.oldName = oldName
20 | }
21 | }
22 |
23 | let model: ViewModel
24 |
25 | var body: some View {
26 | if let oldName = model.oldName {
27 | return GenericEventView(text: L10n.Event.RoomName.changeName(model.sender, oldName, model.newName))
28 | }
29 | return GenericEventView(text: L10n.Event.RoomName.setName(model.sender, model.newName))
30 | }
31 | }
32 |
33 | struct RoomNameEventView_Previews: PreviewProvider {
34 | static var previews: some View {
35 | VStack {
36 | RoomNameEventView(model: .init(sender: "Jane", newName: "New Room", oldName: nil))
37 | RoomNameEventView(model: .init(sender: "Jane", newName: "New Room", oldName: "Old Room"))
38 | }
39 | .previewLayout(.sizeThatFits)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/RoomPowerLevelsEventView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import class MatrixSDK.MXEvent
3 |
4 | struct RoomPowerLevelsEventView: View {
5 | struct ViewModel {
6 | fileprivate let sender: String
7 |
8 | struct LevelChange: Identifiable {
9 | let name: String
10 | let old: Int?
11 | let new: Int
12 |
13 | var id: String { name }
14 |
15 | private func levelName(_ level: Int) -> String {
16 | switch level {
17 | case 0: return L10n.RoomPowerLevel.default
18 | case 50: return L10n.RoomPowerLevel.moderator
19 | case 100: return L10n.RoomPowerLevel.admin
20 | default: return L10n.RoomPowerLevel.custom(level)
21 | }
22 | }
23 |
24 | fileprivate var oldLevelName: String {
25 | old.map { levelName($0) } ?? L10n.RoomPowerLevel.default
26 | }
27 |
28 | fileprivate var newLevelName: String {
29 | levelName(new)
30 | }
31 | }
32 | private let changes: [LevelChange]
33 |
34 | init(sender: String,
35 | changes: [LevelChange]) {
36 | self.sender = sender
37 | self.changes = changes
38 | }
39 |
40 | init(event: MXEvent) {
41 | let previousUsers = (event.unsignedData?.prevContent?["users"] as? [String: Int]) ?? [:]
42 | let currentUsers = (event.content?["users"] as? [String: Int]) ?? [:]
43 |
44 | var changes: [LevelChange] = []
45 | for (user, level) in currentUsers {
46 | if let oldLevel = previousUsers[user] {
47 | if oldLevel != level {
48 | changes.append(LevelChange(name: user,
49 | old: oldLevel,
50 | new: level))
51 | }
52 | } else {
53 | changes.append(LevelChange(name: user,
54 | old: nil,
55 | new: level))
56 | }
57 | }
58 |
59 | let sender = event.sender ?? ""
60 | self.init(sender: sender, changes: changes)
61 | }
62 |
63 | var combined: String {
64 | changes
65 | .map {
66 | if sender == $0.name {
67 | return L10n.Event.RoomPowerLevel.changeSelf(sender, $0.oldLevelName, $0.newLevelName)
68 | }
69 | return L10n.Event.RoomPowerLevel.changeOther(sender, $0.name, $0.oldLevelName, $0.newLevelName)
70 | }
71 | .joined(separator: "\n")
72 | }
73 | }
74 |
75 | let model: ViewModel
76 |
77 | var body: some View {
78 | GenericEventView(text: model.combined)
79 | }
80 | }
81 |
82 | struct RoomPowerLevelsEventView_Previews: PreviewProvider {
83 | static var previews: some View {
84 | RoomPowerLevelsEventView(model: .init(
85 | sender: "Jane",
86 | changes: [
87 | .init(name: "John", old: nil, new: 50),
88 | .init(name: "John", old: 50, new: 100),
89 | ]))
90 | .previewLayout(.sizeThatFits)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Nio/Conversations/Event Views/RoomTopicEventView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import class MatrixSDK.MXEvent
3 |
4 | struct RoomTopicEventView: View {
5 | struct ViewModel {
6 | let sender: String
7 | let topic: String
8 |
9 | init(sender: String, topic: String) {
10 | self.sender = sender
11 | self.topic = topic
12 | }
13 |
14 | init(event: MXEvent) {
15 | self.init(sender: event.sender ?? L10n.Event.unknownSenderFallback,
16 | topic: event.content(valueFor: "topic") ?? "")
17 | }
18 | }
19 |
20 | let model: ViewModel
21 |
22 | var body: some View {
23 | GenericEventView(text: L10n.Event.RoomTopic.change(model.sender, model.topic))
24 | }
25 | }
26 |
27 | struct RoomTopicEventView_Previews: PreviewProvider {
28 | static var previews: some View {
29 | RoomTopicEventView(model: .init(sender: "Jane Doe", topic: "The Orville"))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Nio/Conversations/TypingIndicatorView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | import NioKit
4 |
5 | struct TypingIndicatorContainerView: View {
6 | @EnvironmentObject private var room: NIORoom
7 | @Environment(\.userId) private var userId
8 |
9 | private var typingUsers: [String] {
10 | room.room.typingUsers?.filter { $0 != userId} ?? []
11 | }
12 |
13 | var body: some View {
14 | TypingIndicatorView(typingUsers: typingUsers)
15 | }
16 | }
17 |
18 | struct TypingIndicatorView: View {
19 | @Environment(\.colorScheme) var colorScheme: ColorScheme
20 | let typingUsers: [String]
21 |
22 | private var text: String {
23 | switch typingUsers.count {
24 | case 1:
25 | return L10n.TypingIndicator.single(typingUsers.first!)
26 | case 2:
27 | return L10n.TypingIndicator.two(typingUsers[0], typingUsers[1])
28 | default:
29 | return L10n.TypingIndicator.many
30 | }
31 | }
32 |
33 | private var backgroundColor: Color {
34 | switch self.colorScheme {
35 | case .dark:
36 | return Color(#colorLiteral(red: 0.1221848231, green: 0.1316168257, blue: 0.1457917546, alpha: 1))
37 | default:
38 | return Color(#colorLiteral(red: 0.968541801, green: 0.9726034999, blue: 0.9763545394, alpha: 1))
39 | }
40 | }
41 |
42 | var body: some View {
43 | HStack {
44 | Group {
45 | SFSymbol.typing
46 | Text(text)
47 | }
48 | .font(.caption)
49 | .foregroundColor(.gray)
50 | Spacer()
51 | }
52 | .padding(8)
53 | .background(self.backgroundColor)
54 | }
55 | }
56 |
57 | struct TypingIndicatorView_Previews: PreviewProvider {
58 | static var previews: some View {
59 | Group {
60 | TypingIndicatorView(typingUsers: ["Jane Doe"])
61 | TypingIndicatorView(typingUsers: ["Jane Doe", "John Doe"])
62 | TypingIndicatorView(typingUsers: ["Jane Doe", "John Doe", "Jill Doe"])
63 |
64 | TypingIndicatorView(typingUsers: ["Jane Doe", "John Doe"])
65 | .environment(\.colorScheme, .dark)
66 |
67 | TypingIndicatorView(typingUsers: ["Jane Doe", "John Doe"])
68 | .environment(\.sizeCategory, .accessibilityLarge)
69 | }
70 | .previewLayout(.sizeThatFits)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Nio/Extensions/Color+Named.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | extension Color {
5 | static var borderedMessageBackground: Color = .init(Asset.Color.borderedMessageBackground.name)
6 |
7 | static func backgroundColor(for colorScheme: ColorScheme) -> Color {
8 | if colorScheme == .dark {
9 | return .black
10 | } else {
11 | return .white
12 | }
13 | }
14 | }
15 |
16 | extension UXColor {
17 | /// Color of text that is shown on top of the accent color, e.g. badges.
18 | static func textOnAccentColor(for colorScheme: ColorScheme) -> UXColor {
19 | messageTextColor(for: colorScheme, isOutgoing: true)
20 | }
21 |
22 | static func messageTextColor(for colorScheme: ColorScheme,
23 | isOutgoing: Bool) -> UXColor {
24 | if isOutgoing {
25 | return .white
26 | }
27 | switch colorScheme {
28 | case .dark:
29 | return .white
30 | default:
31 | return .black
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Nio/Extensions/Color+allAccent.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension Color: RawRepresentable {
4 | static var allAccentOptions: [Color] {
5 | [
6 | .purple,
7 | .blue,
8 | .red,
9 | .orange,
10 | .green,
11 | .gray,
12 | .yellow
13 | ]
14 | }
15 |
16 | public init?(rawValue: String) {
17 | switch rawValue {
18 | case "purple": self = .purple
19 | case "blue": self = .blue
20 | case "red": self = .red
21 | case "orange": self = .orange
22 | case "green": self = .green
23 | case "gray": self = .gray
24 | case "yellow": self = .yellow
25 | default: self = .purple
26 | }
27 | }
28 |
29 | public var rawValue: String { description }
30 | }
31 |
--------------------------------------------------------------------------------
/Nio/Extensions/ContentSizeCategory.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension ContentSizeCategory {
4 | var scalingFactor: CGFloat {
5 | switch self {
6 | case .extraSmall:
7 | return 0.8
8 | case .small:
9 | return 0.9
10 | case .medium:
11 | return 1.0
12 | case .large:
13 | return 1.1
14 | case .extraLarge:
15 | return 1.2
16 | case .extraExtraLarge:
17 | return 1.3
18 | case .extraExtraExtraLarge:
19 | return 1.4
20 | case .accessibilityMedium:
21 | return 1.5
22 | case .accessibilityLarge:
23 | return 1.6
24 | case .accessibilityExtraLarge:
25 | return 1.7
26 | case .accessibilityExtraExtraLarge:
27 | return 1.8
28 | case .accessibilityExtraExtraExtraLarge:
29 | return 1.9
30 | @unknown default:
31 | return 0.0
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Nio/Extensions/EnvironmentValues.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct UserIDKey: EnvironmentKey {
4 | static let defaultValue: String = ""
5 | }
6 |
7 | struct HomeserverKey: EnvironmentKey {
8 | static let defaultValue = URL(string: "https://matrix.org")!
9 | }
10 |
11 | extension EnvironmentValues {
12 | var userId: String {
13 | get {
14 | return self[UserIDKey.self]
15 | }
16 | set {
17 | self[UserIDKey.self] = newValue
18 | }
19 | }
20 |
21 | var homeserver: URL {
22 | get {
23 | return self[HomeserverKey.self]
24 | }
25 | set {
26 | self[HomeserverKey.self] = newValue
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Nio/Extensions/NSAttributedString+Extensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension NSAttributedString {
4 | var isEmpty: Bool {
5 | self.length == 0
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Nio/Extensions/PreviewProvider+Enumeration.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension PreviewProvider {
4 | static func enumeratingColorSchemes(
5 | _ colorSchemes: [ColorScheme] = ColorScheme.allCases,
6 | _ content: @escaping () -> Content
7 | ) -> some View where Content: View {
8 | ForEach(colorSchemes, id: \.self) { colorScheme in
9 | Group {
10 | content()
11 | }
12 | .environment(\.colorScheme, colorScheme)
13 | .background(Color.backgroundColor(for: colorScheme))
14 | }
15 | }
16 |
17 | static func enumeratingSizeCategories(
18 | _ sizeCategories: [ContentSizeCategory] = ContentSizeCategory.allCases,
19 | _ content: @escaping () -> Content
20 | ) -> some View where Content: View {
21 | Group {
22 | List {
23 | ForEach(sizeCategories, id: \.self) { sizeCategory in
24 | content().environment(\.sizeCategory, sizeCategory)
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Nio/Extensions/String+Emoji.swift:
--------------------------------------------------------------------------------
1 | // via https://stackoverflow.com/a/39425959/1843020
2 |
3 | extension Character {
4 | /// A simple emoji is one scalar and presented to the user as an Emoji
5 | var isSimpleEmoji: Bool {
6 | guard let firstProperties = unicodeScalars.first?.properties else {
7 | return false
8 | }
9 | return unicodeScalars.count == 1 &&
10 | (firstProperties.isEmojiPresentation ||
11 | firstProperties.generalCategory == .otherSymbol)
12 | }
13 |
14 | /// Checks if the scalars will be merged into an emoji
15 | var isCombinedIntoEmoji: Bool {
16 | return unicodeScalars.count > 1 &&
17 | unicodeScalars.contains { $0.properties.isJoinControl || $0.properties.isVariationSelector }
18 | }
19 |
20 | var isEmoji: Bool {
21 | return isSimpleEmoji || isCombinedIntoEmoji
22 | }
23 | }
24 |
25 | extension String {
26 | var isSingleEmoji: Bool {
27 | return count == 1 && containsEmoji
28 | }
29 |
30 | var containsEmoji: Bool {
31 | return contains { $0.isEmoji }
32 | }
33 |
34 | var containsOnlyEmoji: Bool {
35 | return !isEmpty && !contains { !$0.isEmoji }
36 | }
37 |
38 | var emojiString: String {
39 | return emojis.map { String($0) }.reduce("", +)
40 | }
41 |
42 | var emojis: [Character] {
43 | return filter { $0.isEmoji }
44 | }
45 |
46 | var emojiScalars: [UnicodeScalar] {
47 | return filter { $0.isEmoji }.flatMap { $0.unicodeScalars }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Nio/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // FIXME: this seems like it could back-fire,
4 | // encouraging the use of stringly-typed code.
5 | extension String: Identifiable {
6 | public var id: String {
7 | self
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Nio/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AppGroup
6 | $(APPGROUP)
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIcons
12 |
13 | CFBundleAlternateIcons
14 |
15 | Six Colors Dark
16 |
17 | CFBundleIconFiles
18 |
19 | Six Colors Dark
20 |
21 | UIPrerenderedIcon
22 |
23 |
24 | Six Colors Light
25 |
26 | CFBundleIconFiles
27 |
28 | Six Colors Light
29 |
30 | UIPrerenderedIcon
31 |
32 |
33 | Sketch
34 |
35 | CFBundleIconFiles
36 |
37 | Sketch
38 |
39 | UIPrerenderedIcon
40 |
41 |
42 |
43 | CFBundlePrimaryIcon
44 |
45 | CFBundleIconFiles
46 |
47 | AppIcon
48 |
49 | UIPrerenderedIcon
50 |
51 |
52 |
53 | CFBundleIdentifier
54 | $(PRODUCT_BUNDLE_IDENTIFIER)
55 | CFBundleInfoDictionaryVersion
56 | 6.0
57 | CFBundleName
58 | $(PRODUCT_NAME)
59 | CFBundlePackageType
60 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
61 | CFBundleShortVersionString
62 | $(MARKETING_VERSION)
63 | CFBundleVersion
64 | $(CURRENT_PROJECT_VERSION)
65 | LSRequiresIPhoneOS
66 |
67 | UIApplicationSceneManifest
68 |
69 | UIApplicationSupportsMultipleScenes
70 |
71 |
72 | UILaunchStoryboardName
73 | LaunchScreen
74 | UIRequiredDeviceCapabilities
75 |
76 | armv7
77 |
78 | UISupportedInterfaceOrientations
79 |
80 | UIInterfaceOrientationPortrait
81 | UIInterfaceOrientationLandscapeLeft
82 | UIInterfaceOrientationLandscapeRight
83 |
84 | DevelopmentTeam
85 | $(DEVELOPMENT_TEAM)
86 | UISupportedInterfaceOrientations~ipad
87 |
88 | UIInterfaceOrientationPortrait
89 | UIInterfaceOrientationPortraitUpsideDown
90 | UIInterfaceOrientationLandscapeLeft
91 | UIInterfaceOrientationLandscapeRight
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/Nio/Nio.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.$(APPGROUP)
10 |
11 | com.apple.security.network.client
12 |
13 | keychain-access-groups
14 |
15 | $(AppIdentifierPrefix)nio.keychain
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Nio/NioApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | @main
5 | struct NioApp: App {
6 | @StateObject private var accountStore = AccountStore()
7 |
8 | @AppStorage("accentColor") private var accentColor: Color = .purple
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | #if os(macOS)
13 | RootView()
14 | .environmentObject(accountStore)
15 | .accentColor(accentColor)
16 | .frame(minWidth: Style.minWindowWidth, minHeight: Style.minWindowHeight)
17 | .presentedWindowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: false))
18 | #else
19 | RootView()
20 | .environmentObject(accountStore)
21 | .accentColor(accentColor)
22 | #endif
23 | }
24 |
25 | #if os(macOS)
26 | Settings {
27 | SettingsContainerView()
28 | .environmentObject(accountStore)
29 | }
30 | #endif
31 | }
32 | }
33 |
34 | #if os(macOS)
35 | enum Style {
36 | static let minSidebarWidth = 280 as CGFloat
37 | static let minTimelineWidth = 480 as CGFloat
38 | static let minWindowWidth = minSidebarWidth + minTimelineWidth + 10
39 | static let minWindowHeight = 320 as CGFloat
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/Nio/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Six Colors Dark/Six Colors Dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Six Colors Dark/Six Colors Dark@2x.png
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Six Colors Dark/Six Colors Dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Six Colors Dark/Six Colors Dark@3x.png
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Six Colors Light/Six Colors Light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Six Colors Light/Six Colors Light@2x.png
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Six Colors Light/Six Colors Light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Six Colors Light/Six Colors Light@3x.png
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Sketch/Sketch@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Sketch/Sketch@2x.png
--------------------------------------------------------------------------------
/Nio/Resources/Alternate Icons/Sketch/Sketch@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Nio/Resources/Alternate Icons/Sketch/Sketch@3x.png
--------------------------------------------------------------------------------
/Nio/RootView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | import NioKit
4 |
5 | struct RootView: View {
6 | @EnvironmentObject private var store: AccountStore
7 |
8 | private var homeserverURL: URL {
9 | // Can this ever be nil? And if so, what happens with the default fallback?
10 | assert(store.client != nil)
11 | let configuredURL = store.client?.homeserver.flatMap(URL.init)
12 | assert(configuredURL != nil)
13 | return configuredURL ?? HomeserverKey.defaultValue
14 | }
15 |
16 | var body: some View {
17 | switch store.loginState {
18 | case .loggedIn(let userId):
19 | RecentRoomsContainerView()
20 | .environment(\.userId, userId)
21 | .environment(\.homeserver, homeserverURL)
22 |
23 | case .loggedOut:
24 | LoginContainerView()
25 |
26 | case .authenticating:
27 | LoadingView()
28 |
29 | case .failure(let error):
30 | VStack {
31 | Spacer()
32 | Text(verbatim: error.localizedDescription)
33 | Spacer()
34 | Button(action: { self.store.loginState = .loggedOut }) {
35 | Text(verbatim: L10n.Login.failureBackToLogin)
36 | }
37 | .padding()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Nio/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 |
4 | import NioKit
5 | // swiftlint:disable line_length
6 |
7 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
8 |
9 | var window: UIWindow?
10 |
11 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
12 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
13 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
14 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
15 |
16 | let accountStore = AccountStore()
17 | let appSettings = AppSettings()
18 |
19 | let rootView = RootView()
20 | .environmentObject(accountStore)
21 | .environmentObject(appSettings)
22 | .accentColor(appSettings.accentColor)
23 |
24 | // Use a UIHostingController as window root view controller.
25 | if let windowScene = scene as? UIWindowScene {
26 | let window = UIWindow(windowScene: windowScene)
27 | window.rootViewController = UIHostingController(rootView: rootView)
28 | self.window = window
29 | window.makeKeyAndVisible()
30 | }
31 | }
32 |
33 | func sceneDidDisconnect(_ scene: UIScene) {
34 | // Called as the scene is being released by the system.
35 | // This occurs shortly after the scene enters the background, or when its session is discarded.
36 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
37 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
38 | }
39 |
40 | func sceneDidBecomeActive(_ scene: UIScene) {
41 | // Called when the scene has moved from an inactive state to an active state.
42 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
43 | }
44 |
45 | func sceneWillResignActive(_ scene: UIScene) {
46 | // Called when the scene will move from an active state to an inactive state.
47 | // This may occur due to temporary interruptions (ex. an incoming phone call).
48 | }
49 |
50 | func sceneWillEnterForeground(_ scene: UIScene) {
51 | // Called as the scene transitions from the background to the foreground.
52 | // Use this method to undo the changes made on entering the background.
53 | }
54 |
55 | func sceneDidEnterBackground(_ scene: UIScene) {
56 | // Called as the scene transitions from the foreground to the background.
57 | // Use this method to save data, release shared resources, and store enough scene-specific state information
58 | // to restore the scene back to its current state.
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Nio/Settings/AppIcon.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct AppIcon: Identifiable, View {
4 | let title: String
5 | let attribution: String?
6 |
7 | var id: String {
8 | title
9 | }
10 |
11 | var previewName: String {
12 | "App Icons/\(title)"
13 | }
14 |
15 | init(title: String, attribution: String? = nil) {
16 | self.title = title
17 | self.attribution = attribution
18 | }
19 |
20 | var body: some View {
21 | HStack {
22 | Image(previewName)
23 | .resizable()
24 | .frame(width: 60, height: 60)
25 | .cornerRadius(12)
26 | .padding(5)
27 | VStack(alignment: .leading) {
28 | Text(title)
29 | if attribution != nil {
30 | Text(attribution!)
31 | .font(.footnote)
32 | .foregroundColor(.gray)
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | #if os(macOS)
40 | class AppIconTitle: ObservableObject {
41 | static var alternatives = [ "Default" ]
42 |
43 | var current: String {
44 | get { return Self.alternatives.first ?? "" }
45 | set {
46 | assert(newValue == Self.alternatives.first)
47 | }
48 | }
49 | }
50 | #else // iOS
51 | class AppIconTitle: ObservableObject {
52 | static var alternatives = [
53 | "Default",
54 | "Six Colors Dark",
55 | "Six Colors Light",
56 | "Sketch",
57 | ]
58 |
59 | var current: String {
60 | get {
61 | UIApplication.shared.alternateIconName ?? "Default"
62 | }
63 | set {
64 | var iconName: String? = newValue
65 | if iconName == "Default" {
66 | iconName = nil
67 | }
68 | objectWillChange.send()
69 | UIApplication.shared.setAlternateIconName(iconName) { error in
70 | guard let error = error else { return }
71 | print("Error setting new app icon: \(error)")
72 | }
73 | }
74 | }
75 | }
76 | #endif // iOS
77 |
--------------------------------------------------------------------------------
/Nio/Settings/SettingsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | struct SettingsContainerView: View {
5 | @EnvironmentObject var store: AccountStore
6 |
7 | var body: some View {
8 | #if os(macOS)
9 | MacSettingsView(logoutAction: self.store.logout)
10 | #else
11 | SettingsView(logoutAction: self.store.logout)
12 | #endif
13 | }
14 | }
15 |
16 | private struct MacSettingsView: View {
17 | @AppStorage("accentColor") private var accentColor: Color = .purple
18 | let logoutAction: () -> Void
19 |
20 | var body: some View {
21 | Form {
22 | Section {
23 | Picker(selection: $accentColor, label: Text(verbatim: L10n.Settings.accentColor)) {
24 | ForEach(Color.allAccentOptions, id: \.self) { color in
25 | HStack {
26 | Circle()
27 | .frame(width: 20)
28 | .foregroundColor(color)
29 | Text(color.description.capitalized)
30 | }
31 | .tag(color)
32 | }
33 | }
34 | // No icon picker on macOS
35 | }
36 |
37 | Section {
38 | Button(action: self.logoutAction) {
39 | Text(verbatim: L10n.Settings.logOut)
40 | }
41 | }
42 | }
43 | .padding()
44 | .frame(maxWidth: 320)
45 | }
46 | }
47 |
48 | private struct SettingsView: View {
49 | @AppStorage("accentColor") private var accentColor: Color = .purple
50 | @StateObject private var appIconTitle = AppIconTitle()
51 | let logoutAction: () -> Void
52 |
53 | @Environment(\.presentationMode) private var presentationMode
54 |
55 | var body: some View {
56 | NavigationView {
57 | Form {
58 | Section {
59 | Picker(selection: $accentColor, label: Text(verbatim: L10n.Settings.accentColor)) {
60 | ForEach(Color.allAccentOptions, id: \.self) { color in
61 | HStack {
62 | Circle()
63 | .frame(width: 20)
64 | .foregroundColor(color)
65 | Text(color.description.capitalized)
66 | }
67 | .tag(color)
68 | }
69 | }
70 |
71 | Picker(selection: $appIconTitle.current, label: Text(verbatim: L10n.Settings.appIcon)) {
72 | ForEach(AppIconTitle.alternatives) { AppIcon(title: $0) }
73 | }
74 | }
75 |
76 | Section {
77 | Button(action: self.logoutAction) {
78 | Text(verbatim: L10n.Settings.logOut)
79 | }
80 | }
81 | }
82 | .navigationBarTitle(L10n.Settings.title, displayMode: .inline)
83 | .toolbar {
84 | ToolbarItem(placement: .confirmationAction) {
85 | Button(L10n.Settings.dismiss) {
86 | presentationMode.wrappedValue.dismiss()
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | struct SettingsView_Previews: PreviewProvider {
95 | static var previews: some View {
96 | SettingsView(logoutAction: {})
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Nio/Shapes/IndividuallyRoundedRectangle.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct IndividuallyRoundedRectangle: Shape {
4 | var topLeft: CGFloat = 0.0
5 | var topRight: CGFloat = 0.0
6 | var bottomLeft: CGFloat = 0.0
7 | var bottomRight: CGFloat = 0.0
8 |
9 | func path(in rect: CGRect) -> Path {
10 | var path = Path()
11 |
12 | let width = rect.size.width
13 | let height = rect.size.height
14 |
15 | let topRight = min(min(self.topRight, height * 0.5), width * 0.5)
16 | let topLeft = min(min(self.topLeft, height * 0.5), width * 0.5)
17 | let bottomLeft = min(min(self.bottomLeft, height * 0.5), width * 0.5)
18 | let bottomRight = min(min(self.bottomRight, height * 0.5), width * 0.5)
19 |
20 | path.move(to: CGPoint(x: width * 0.5, y: 0.0))
21 |
22 | path.addLine(to: CGPoint(x: width - topRight, y: 0.0))
23 | path.addArc(
24 | center: CGPoint(x: width - topRight, y: topRight),
25 | radius: topRight,
26 | startAngle: Angle(degrees: -90.0),
27 | endAngle: Angle(degrees: 0.0),
28 | clockwise: false
29 | )
30 |
31 | path.addLine(to: CGPoint(x: width, y: height - bottomRight))
32 | path.addArc(
33 | center: CGPoint(x: width - bottomRight, y: height - bottomRight),
34 | radius: bottomRight,
35 | startAngle: Angle(degrees: 0.0),
36 | endAngle: Angle(degrees: 90.0),
37 | clockwise: false
38 | )
39 |
40 | path.addLine(to: CGPoint(x: bottomLeft, y: height))
41 | path.addArc(
42 | center: CGPoint(x: bottomLeft, y: height - bottomLeft),
43 | radius: bottomLeft,
44 | startAngle: Angle(degrees: 90.0),
45 | endAngle: Angle(degrees: 180.0),
46 | clockwise: false
47 | )
48 |
49 | path.addLine(to: CGPoint(x: 0.0, y: topLeft))
50 | path.addArc(
51 | center: CGPoint(x: topLeft, y: topLeft),
52 | radius: topLeft,
53 | startAngle: Angle(degrees: 180.0),
54 | endAngle: Angle(degrees: 270.0),
55 | clockwise: false
56 | )
57 |
58 | return path
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Nio/Shared Views/ImagePicker.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | #if os(macOS)
4 |
5 | struct ImagePicker: View {
6 |
7 | var body: some View {
8 | Text("Sorrz, no image picker on macOS yet :-/")
9 | }
10 | }
11 |
12 | #else // iOS
13 | struct ImagePicker: UIViewControllerRepresentable {
14 |
15 | @Environment(\.presentationMode)
16 | private var presentationMode
17 |
18 | let sourceType: UIImagePickerController.SourceType
19 | let onImagePicked: (UIImage) -> Void
20 |
21 | final class Coordinator: NSObject,
22 | UINavigationControllerDelegate,
23 | UIImagePickerControllerDelegate {
24 |
25 | @Binding
26 | private var presentationMode: PresentationMode
27 | private let sourceType: UIImagePickerController.SourceType
28 | private let onImagePicked: (UIImage) -> Void
29 |
30 | init(presentationMode: Binding,
31 | sourceType: UIImagePickerController.SourceType,
32 | onImagePicked: @escaping (UIImage) -> Void) {
33 | _presentationMode = presentationMode
34 | self.sourceType = sourceType
35 | self.onImagePicked = onImagePicked
36 | }
37 |
38 | func imagePickerController(_ picker: UIImagePickerController,
39 | didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
40 | // swiftlint:disable force_cast
41 | let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
42 | // swiftlint:enable force_cast
43 | onImagePicked(uiImage)
44 | presentationMode.dismiss()
45 |
46 | }
47 |
48 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
49 | presentationMode.dismiss()
50 | }
51 |
52 | }
53 |
54 | func makeCoordinator() -> Coordinator {
55 | return Coordinator(presentationMode: presentationMode,
56 | sourceType: sourceType,
57 | onImagePicked: onImagePicked)
58 | }
59 |
60 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController {
61 | let picker = UIImagePickerController()
62 | picker.sourceType = sourceType
63 | picker.delegate = context.coordinator
64 | return picker
65 | }
66 |
67 | func updateUIViewController(_ uiViewController: UIImagePickerController,
68 | context: UIViewControllerRepresentableContext) {
69 |
70 | }
71 |
72 | }
73 | #endif // iOS
74 |
--------------------------------------------------------------------------------
/Nio/Shared Views/MessageTextViewWrapper.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | #if os(macOS)
5 | class MessageTextView: NSTextView {
6 | convenience init(attributedString: NSAttributedString, linkColor: UXColor,
7 | maxSize: CGSize)
8 | {
9 | self.init()
10 | backgroundColor = .clear
11 | textContainerInset = .zero
12 | isEditable = false
13 | linkTextAttributes = [
14 | .foregroundColor: linkColor,
15 | .underlineStyle: NSUnderlineStyle.single.rawValue,
16 | ]
17 |
18 | self.insertText(attributedString,
19 | replacementRange: NSRange(location: 0, length: 0))
20 | self.maxSize = maxSize
21 |
22 | // don't resist text wrapping across multiple lines
23 | setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
24 | }
25 | }
26 |
27 | struct MessageTextViewWrapper: NSViewRepresentable {
28 | let attributedString: NSAttributedString
29 | let linkColor: NSColor
30 | let maxSize: CGSize
31 |
32 | func makeNSView(context: Context) -> MessageTextView {
33 | MessageTextView(attributedString: attributedString, linkColor: linkColor, maxSize: maxSize)
34 | }
35 |
36 | func updateNSView(_ uiView: MessageTextView, context: Context) {
37 | // nothing to update
38 | }
39 |
40 | func makeCoordinator() {
41 | // nothing to coordinate
42 | }
43 | }
44 | #else // iOS
45 | /// An automatically sized label, which allows links to be tapped.
46 | class MessageTextView: UITextView {
47 | var maxSize: CGSize = .zero
48 |
49 | // Allows SwiftUI to automatically size the label appropriately
50 | override var intrinsicContentSize: CGSize {
51 | sizeThatFits(CGSize(width: maxSize.width, height: .infinity))
52 | }
53 |
54 | convenience init(attributedString: NSAttributedString, linkColor: UIColor, maxSize: CGSize) {
55 | self.init()
56 |
57 | font = UIFont.preferredFont(forTextStyle: .body)
58 | textColor = UIColor.label
59 | backgroundColor = .clear
60 | textContainerInset = .zero
61 | textContainer.lineFragmentPadding = 0
62 | dataDetectorTypes = .all
63 | isEditable = false
64 | isScrollEnabled = false
65 | linkTextAttributes = [
66 | .foregroundColor: linkColor,
67 | .underlineStyle: NSUnderlineStyle.single.rawValue,
68 | ]
69 |
70 | attributedText = attributedString
71 | self.maxSize = maxSize
72 |
73 | // don't resist text wrapping across multiple lines
74 | setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
75 | }
76 | }
77 |
78 | struct MessageTextViewWrapper: UIViewRepresentable {
79 | let attributedString: NSAttributedString
80 | let linkColor: UIColor
81 | let maxSize: CGSize
82 |
83 | func makeUIView(context: Context) -> MessageTextView {
84 | MessageTextView(attributedString: attributedString, linkColor: linkColor, maxSize: maxSize)
85 | }
86 |
87 | func updateUIView(_ uiView: MessageTextView, context: Context) {
88 | // nothing to update
89 | }
90 |
91 | func makeCoordinator() {
92 | // nothing to coordinate
93 | }
94 | }
95 | #endif // iOS
96 |
--------------------------------------------------------------------------------
/Nio/Shared Views/MultilineTextField.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 |
4 | struct MultilineTextField: View {
5 | @Binding private var attributedText: NSAttributedString
6 | @Binding private var isEditing: Bool
7 |
8 | @State private var contentSizeThatFits: CGSize = .zero
9 |
10 | private let placeholder: String
11 | private let textAttributes: TextAttributes
12 |
13 | private let onEditingChanged: ((Bool) -> Void)?
14 | private let onCommit: (() -> Void)?
15 |
16 | private var placeholderInset: EdgeInsets {
17 | .init(top: 8.0, leading: 8.0, bottom: 8.0, trailing: 8.0)
18 | }
19 |
20 | private var textContainerInset: UXEdgeInsets {
21 | .init(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
22 | }
23 |
24 | private var lineFragmentPadding: CGFloat {
25 | 8.0
26 | }
27 |
28 | init (
29 | attributedText: Binding,
30 | placeholder: String = "",
31 | isEditing: Binding,
32 | textAttributes: TextAttributes = .init(),
33 | onEditingChanged: ((Bool) -> Void)? = nil,
34 | onCommit: (() -> Void)? = nil
35 | ) {
36 | self._attributedText = attributedText
37 | self.placeholder = placeholder
38 |
39 | self._isEditing = isEditing
40 |
41 | self._contentSizeThatFits = State(initialValue: .zero)
42 |
43 | self.textAttributes = textAttributes
44 |
45 | self.onEditingChanged = onEditingChanged
46 | self.onCommit = onCommit
47 | }
48 |
49 | #if os(macOS)
50 | var plainStringBinding : Binding {
51 | return .init(get: { attributedText.string },
52 | set: { attributedText = NSAttributedString(string: $0) })
53 | }
54 | #endif
55 |
56 | var body: some View {
57 | #if os(macOS)
58 | TextField("Compose message",
59 | text: plainStringBinding,
60 | onEditingChanged: onEditingChanged ?? { _ in },
61 | onCommit: onCommit ?? {})
62 | .textFieldStyle(RoundedBorderTextFieldStyle())
63 | #else // iOS
64 | AttributedText(
65 | attributedText: $attributedText,
66 | isEditing: $isEditing,
67 | textAttributes: textAttributes,
68 | onEditingChanged: onEditingChanged,
69 | onCommit: onCommit
70 | )
71 | .onPreferenceChange(ContentSizeThatFitsKey.self) {
72 | self.contentSizeThatFits = $0
73 | }
74 | .frame(
75 | idealHeight: self.contentSizeThatFits.height
76 | )
77 | .background(placeholderView, alignment: .topLeading)
78 | #endif // iOS
79 | }
80 |
81 | @ViewBuilder private var placeholderView: some View {
82 | if attributedText.isEmpty {
83 | Text(placeholder).foregroundColor(.gray)
84 | .padding(placeholderInset)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Nio/Shared Views/ReverseList.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct IsVisibleKey: PreferenceKey {
4 | static var defaultValue: Bool = false
5 |
6 | static func reduce(value: inout Bool, nextValue: () -> Bool) {
7 | value = nextValue()
8 | }
9 | }
10 |
11 | struct ReverseList: View where Element: Identifiable, Content: View {
12 | private let items: [Element]
13 | private let reverseItemOrder: Bool
14 | private let viewForItem: (Element) -> Content
15 |
16 | @Binding var hasReachedTop: Bool
17 |
18 | init(_ items: [Element], reverseItemOrder: Bool = true, hasReachedTop: Binding, viewForItem: @escaping (Element) -> Content) {
19 | self.items = items
20 | self.reverseItemOrder = reverseItemOrder
21 | self._hasReachedTop = hasReachedTop
22 | self.viewForItem = viewForItem
23 | }
24 |
25 | var body: some View {
26 | GeometryReader { contentsGeometry in
27 | ScrollView {
28 | ForEach(reverseItemOrder ? items.reversed() : items) { item in
29 | self.viewForItem(item)
30 | .scaleEffect(x: -1.0, y: 1.0)
31 | .rotationEffect(.degrees(180))
32 | }
33 | GeometryReader { topViewGeometry in
34 | let frame = topViewGeometry.frame(in: .global)
35 | let isVisible = contentsGeometry.frame(in: .global).contains(CGPoint(x: frame.midX, y: frame.midY))
36 |
37 | HStack {
38 | Spacer()
39 | ProgressView().progressViewStyle(CircularProgressViewStyle())
40 | Spacer()
41 | }
42 | .preference(key: IsVisibleKey.self, value: isVisible)
43 | }
44 | .frame(height: 30) // FIXME: Frame height shouldn't be hard-coded
45 | .onPreferenceChange(IsVisibleKey.self) {
46 | hasReachedTop = $0
47 | }
48 | }
49 | .scaleEffect(x: -1.0, y: 1.0)
50 | .rotationEffect(.degrees(180))
51 | }
52 | }
53 | }
54 |
55 | struct ReverseList_Previews: PreviewProvider {
56 | static var previews: some View {
57 | ReverseList(["1", "2", "3"], hasReachedTop: .constant(false)) {
58 | Text($0)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Nio/Shared Views/SFSymbol.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | enum SFSymbol: String, View {
4 | case typing = "scribble.variable"
5 | case close = "xmark"
6 |
7 | case newConversation = "square.and.pencil"
8 | case settings = "gear"
9 | case send = "paperplane"
10 | case attach = "paperclip"
11 |
12 | var body: some View {
13 | Image(systemName: self.rawValue)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/bg.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | "login.open-registration-prompt" = "Все още нямате профил?";
5 | "login.sign-in" = "Вписване";
6 | "login.form.homeserver-optional-explanation" = "Домашния сървър не е задължителен, ако ползвате matrix.org.";
7 | "login.form.homeserver" = "Домашен сървър";
8 | "login.form.password" = "Парола";
9 | "login.form.username" = "Потребителско име";
10 | "login.welcome-message" = "Влезте в профила си, за да започнете.";
11 | "login.welcome-header" = "Добре дошли в ";
12 | "recent-rooms.leave.alert-title" = "Напускане на стая";
13 | "recent-rooms.rooms.header" = "Стаи";
14 | "recent-rooms.accessibility-label.new-conversation" = "Нов разговор";
15 | "recent-rooms.new-message-placeholder" = "Ново съобщение";
16 | "recent-rooms.accessibility-label.settings" = "Настройки";
17 | "recent-rooms.pending-invitations.leave.alert-title" = "Отхвърляне на поканата?";
18 | "recent-rooms.accessibility-label.new-message-badge" = "%u нови съобщения";
19 | // Room $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE
20 | "recent-rooms.accessibility-label.room" = "Стая %@, %@ %@";
21 | "loading.cancel" = "Отмени";
22 | "loading.4" = "Възстановяване на сесията";
23 | "loading.3" = "Вписване";
24 | "login.failure-back-to-login" = "Обратно към Впиши се";
25 | "login.register-not-yet-implemented" = "Регистрацията на нов акаунт все още не е налична.";
26 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/fi.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
3 | "new-conversation.done" = "Valmis";
4 | "new-conversation.edit" = "Muokkaa";
5 | "new-conversation.cancel" = "Peruuta";
6 | "new-conversation.alert-failed" = "Keskustelun aloittaminen epäonnistui";
7 | "new-conversation.create-room" = "Aloita keskustelu";
8 | "new-conversation.public-room" = "Julkinen huone";
9 | "new-conversation.room-name" = "Huoneen nimi";
10 | "new-conversation.title-room" = "Uusi huone";
11 | "new-conversation.title-chat" = "Uusi keskustelu";
12 | "settings.dismiss" = "Valmis";
13 | "settings.log-out" = "Kirjaudu ulos";
14 | "settings.app-icon" = "Sovelluksen kuvake";
15 | "settings.accent-color" = "Korostusväri";
16 | "reaction-picker.title" = "Napauta emojia lähettääksesi reaktion.";
17 | "event.context-menu.remove" = "Poista";
18 | "event.context-menu.edit" = "Muokkaa";
19 | "event.context-menu.reply" = "Vastaa";
20 | "event.context-menu.add-reaction" = "Lisää reaktio";
21 | "settings.title" = "Asetukset";
22 | "room-power-level.default" = "Oletus";
23 | "event.room-topic.change" = "%@ muutti aiheeksi '%@'";
24 | "event.room-name.change-name" = "%@ muutti huoneen %@ uudeksi nimeksi %@";
25 | "event.room-name.set-name" = "%@ asetti huoneen nimeksi %@";
26 | "event.reason" = "Syy: %@";
27 | "event.room-member.remove-avatar" = "%@ poisti profiilikuvansa";
28 | "event.room-member.change-avatar" = "%@ päivitti profiilikuvansa";
29 | "event.room-member.set-avatar" = "%@ asetti profiilikuvansa";
30 | "event.room-member.change-name" = "%@ vaihtoi näyttönimekseen %@";
31 | "event.room-member.joined" = "%@ liittyi";
32 | "event.room-member.ban" = "%@ esti käyttäjän %@";
33 | "event.room-member.kicked" = "%@ potki käyttäjän %@";
34 | "event.room-member.left" = "%@ poistui";
35 | "event.room-member.invited" = "%@ kutsui käyttäjän %@";
36 | "event.unknown-room-name-fallback" = "Tuntematon";
37 | "event.unknown-sender-fallback" = "Tuntematon";
38 | "typing-indicator.many" = "Useampi ihminen kirjoittaa";
39 | "typing-indicator.two" = "%@ ja %@ kirjoittavat";
40 | "typing-indicator.single" = "%@ kirjoittaa";
41 | "composer.accessibility-label.cancelEdit" = "Peruuta";
42 | "composer.accessibility-label.send" = "Lähetä";
43 | "composer.accessibility-label.send-file" = "Lähetä tiedosto";
44 | "composer.edit-message" = "Muokkaa viestiä:";
45 | "composer.new-message" = "Uusi viesti…";
46 | "room.invitation.join-alert.join-button" = "Liity";
47 | "room.invitation.join-alert.message" = "Hyväksytäänkö kutsu liittyä '%@'?";
48 | "room.invitation.join-alert.title" = "Liitytäänkö keskusteluun?";
49 | "room.invitation.fallback-title" = "Uusi keskustelu";
50 | "room.remove.action" = "Poista";
51 | "room.remove.message" = "Haluatko varmasti poistaa tämän viestin?";
52 | "room.remove.title" = "Poistetaanko?";
53 | "room.attachment.type-photo" = "Kuva";
54 | "room.attachment.select-type" = "Lähetä liite";
55 | "recent-rooms.leave.alert-title" = "Poistu huoneesta";
56 | "recent-rooms.rooms.header" = "Huoneet";
57 | "recent-rooms.pending-invitations.leave.alert-title" = "Hylätäänkö kutsu?";
58 | "recent-rooms.pending-invitations.header" = "Odottavat kutsut";
59 | "recent-rooms.accessibility-label.new-message-badge" = "%u uutta viestiä";
60 | // Room $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE
61 | "recent-rooms.accessibility-label.room" = "Huone %@, %@ %@";
62 | "recent-rooms.new-message-placeholder" = "Uusi viesti";
63 | "recent-rooms.accessibility-label.new-conversation" = "Uusi keskustelu";
64 | "recent-rooms.accessibility-label.settings" = "Asetukset";
65 | "loading.cancel" = "Peruuta";
66 | "loading.4" = "Palautetaan istunto";
67 | "loading.3" = "Kirjaudutaan sisään";
68 | "login.failure-back-to-login" = "Takaisin kirjautumiseen";
69 | "login.register-not-yet-implemented" = "Uusien tilien rekisteröintiä ei ole vielä toteutettu.";
70 | "login.open-registration-prompt" = "Vailla tiliä?";
71 | "login.sign-in" = "Kirjaudu sisään";
72 | "login.form.homeserver-optional-explanation" = "Kotipalvelin on valinnainen, jos käytät matrix.orgia.";
73 | "login.form.homeserver" = "Kotipalvelin";
74 | "login.form.password" = "Salasana";
75 | "login.form.username" = "Käyttäjänimi";
76 | "login.welcome-message" = "Kirjaudu tilillesi aloittaaksesi.";
77 | "login.welcome-header" = "Tervetuloa, tämä on ";
78 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/ko.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/lt.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
3 | "login.form.username" = "Naudotojo vardas";
4 | "login.form.password" = "Slaptažodis";
5 | "login.open-registration-prompt" = "Dar neturite paskyros?";
6 | "login.failure-back-to-login" = "Atgal į prisijungimą";
7 | "recent-rooms.accessibility-label.new-conversation" = "Naujas pokalbis";
8 | "recent-rooms.new-message-placeholder" = "Nauja žinutė";
9 | "recent-rooms.accessibility-label.new-message-badge" = "Naujų žinučių: %u";
10 | "login.welcome-header" = "Sveiki atvykę į ";
11 | "login.welcome-message" = "Norėdami pradėti, prisijunkite prie savo paskyros.";
12 | "login.sign-in" = "Prisijungti";
13 | "login.register-not-yet-implemented" = "Naujų paskyrų registracija kol kas neįgyvendinta.";
14 | "loading.cancel" = "Atsisakyti";
15 | "recent-rooms.accessibility-label.settings" = "Nustatymai";
16 | "composer.accessibility-label.send" = "Siųsti";
17 | "composer.accessibility-label.send-file" = "Siųsti failą";
18 | "composer.edit-message" = "Redaguoti žinutę:";
19 | "composer.new-message" = "Nauja žinutė…";
20 | "room.invitation.join-alert.message" = "Priimti kvietimą prisijungti prie '%@'?";
21 | "room.invitation.join-alert.title" = "Prisijungti prie pokalbio?";
22 | "room.invitation.fallback-title" = "Naujas pokalbis";
23 | "room.remove.action" = "Pašalinti";
24 | "room.attachment.type-photo" = "Nuotrauka";
25 | "recent-rooms.rooms.header" = "Kambariai";
26 | "room.remove.title" = "Pašalinti?";
27 | "room.invitation.join-alert.join-button" = "Prisijungti";
28 | "recent-rooms.leave.alert-title" = "Palikti kambarį";
29 | "login.form.homeserver" = "Namų serveris";
30 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/sq.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "login.welcome-header" = "欢迎来到 ";
2 | "login.welcome-message" = "登录账户来开始使用。";
3 | "login.form.username" = "用户名";
4 | "login.form.password" = "密码";
5 | "login.form.homeserver" = "主服务器";
6 | "login.form.homeserver-optional-explanation" = "若是matrix.org,则不必填写主服务器。";
7 | "login.sign-in" = "登录";
8 | "login.open-registration-prompt" = "还没有账户?";
9 | "login.register-not-yet-implemented" = "新账户注册功能尚未被实现。";
10 | "login.failure-back-to-login" = "返回登录页面";
11 | "loading.1" = "少女祈祷中";
12 | "loading.2" = "正在给长者+1s";
13 | "loading.3" = "登录中";
14 | "loading.4" = "正在恢复会话";
15 | "loading.cancel" = "取消";
16 | "recent-rooms.accessibility-label.settings" = "设置";
17 | "recent-rooms.accessibility-label.new-conversation" = "新对话";
18 | "recent-rooms.new-message-placeholder" = "新信息";
19 | /*DM with $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE*/
20 | "recent-rooms.accessibility-label.dm" = "私聊%@, %@ %@";
21 | /*Room $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE*/
22 | "recent-rooms.accessibility-label.room" = "房间%@, %@ %@";
23 | "recent-rooms.accessibility-label.new-message-badge" = "%u条新信息";
24 | "recent-rooms.pending-invitations.header" = "待决邀请";
25 | "recent-rooms.pending-invitations.leave.alert-title" = "拒绝邀请?";
26 | "recent-rooms.rooms.header" = "房间";
27 | "recent-rooms.leave.alert-title" = "离开房间";
28 | "recent-rooms.leave.alert-body" = "你确定要离开\"%@\"吗?这个操作是不可逆的。";
29 | "room.attachment.select-type" = "发送附件";
30 | "room.attachment.type-photo" = "照片";
31 | "room.remove.title" = "删除?";
32 | "room.remove.message" = "你确定要删除这条消息吗?";
33 | "room.remove.action" = "删除";
34 | "room.invitation.fallback-title" = "新对话";
35 | "room.invitation.join-alert.title" = "加入对话?";
36 | "room.invitation.join-alert.message" = "接受加入'%@'的邀请?";
37 | "room.invitation.join-alert.join-button" = "加入";
38 | "composer.new-message" = "新信息…";
39 | "composer.edit-message" = "编辑信息:";
40 | "composer.accessibility-label.send-file" = "发送文件";
41 | "composer.accessibility-label.send" = "发送";
42 | "composer.accessibility-label.cancelEdit" = "取消";
43 | "typing-indicator.single" = "%@正在输入";
44 | "typing-indicator.two" = "%@和%@正在输入";
45 | "typing-indicator.many" = "多位正在输入";
46 | "event.unknown-sender-fallback" = "未知发送者";
47 | "event.unknown-room-name-fallback" = "未知房间名";
48 | "event.room-member.invited" = "%@邀请了%@";
49 | "event.room-member.left" = "%@离开了房间";
50 | "event.room-member.kicked" = "%@踢出了%@";
51 | "event.room-member.ban" = "%@封禁了%@";
52 | "event.room-member.joined" = "%@加入了房间";
53 | "event.room-member.change-name" = "%@将其显示名称改为了%@";
54 | "event.room-member.set-avatar" = "%@设置了头像";
55 | "event.room-member.change-avatar" = "%@更新了头像";
56 | "event.room-member.remove-avatar" = "%@移除了头像";
57 | "event.room-member.unknown-state" = "未知成员事件状态:%@";
58 | "event.reason" = "原因:%@";
59 | "event.room-name.set-name" = "%@将房间名设为了%@";
60 | "event.room-name.change-name" = "%@将房间名由%@改为了%@";
61 | "event.room-topic.change" = "%@将主题改为了'%@'";
62 | "room-power-level.default" = "默认";
63 | "room-power-level.moderator" = "协管员";
64 | "room-power-level.admin" = "管理员";
65 | "room-power-level.custom" = "自定义(%d)";
66 | "event.room-power-level.change-self" = "%@将其权力等级由%@改为了%@";
67 | "event.room-power-level.change-other" = "%@将%@的权力等级由%@改为了%@";
68 | "event.redaction.redact-self" = "被%@删除的消息";
69 | "event.redaction.redact-other" = "%@删除了%@的消息";
70 | "event.edited" = "已编辑";
71 | "event.context-menu.add-reaction" = "添加反应";
72 | "event.context-menu.reply" = "回复";
73 | "event.context-menu.edit" = "编辑";
74 | "event.context-menu.remove" = "删除";
75 | "reaction-picker.title" = "点击表情以发送反应。";
76 | "settings.title" = "设置";
77 | "settings.accent-color" = "主颜色";
78 | "settings.app-icon" = "应用图标";
79 | "settings.log-out" = "注销";
80 | "settings.dismiss" = "完成";
81 | "new-conversation.username-placeholder" = "Matrix ID";
82 | "new-conversation.cancel" = "取消";
83 | "new-conversation.alert-failed" = "未能开始聊天";
84 | "new-conversation.create-room" = "开始聊天";
85 | "new-conversation.public-room" = "公共房间";
86 | "new-conversation.room-name" = "房间名";
87 | "new-conversation.for-example" = "例如";
88 | "new-conversation.title-room" = "新房间";
89 | "new-conversation.title-chat" = "新聊天";
90 | "new-conversation.done" = "完成";
91 | "new-conversation.edit" = "编辑";
92 |
--------------------------------------------------------------------------------
/Nio/Supporting Files/zh-Hant.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
3 | "event.room-member.joined" = "%@ 已加入";
4 | "event.room-member.ban" = "%@封禁了%@";
5 | "event.room-member.kicked" = "%@將%@踢出";
6 | "event.room-member.left" = "%@ 已離開";
7 | "event.room-member.invited" = "%@邀請了%@";
8 | "event.unknown-room-name-fallback" = "未知傳送人";
9 | "event.unknown-sender-fallback" = "未知聊天室";
10 | "typing-indicator.many" = "有幾位正在打字";
11 | "typing-indicator.two" = "%@ 和 %@ 正在打字";
12 | "typing-indicator.single" = "%@ 正在打字";
13 | "composer.accessibility-label.cancelEdit" = "取消";
14 | "composer.accessibility-label.send" = "傳送";
15 | "composer.accessibility-label.send-file" = "傳送檔案";
16 | "composer.edit-message" = "編輯訊息:";
17 | "composer.new-message" = "新訊息…";
18 | "room.invitation.join-alert.join-button" = "加入";
19 | "room.invitation.join-alert.message" = "接受邀請加入「%@」?";
20 | "room.invitation.join-alert.title" = "加入對話?";
21 | "room.invitation.fallback-title" = "新對話";
22 | "room.remove.action" = "移除";
23 | "room.remove.message" = "您確定要刪除這則訊息嗎?";
24 | "room.remove.title" = "移除?";
25 | "room.attachment.type-photo" = "照片";
26 | "room.attachment.select-type" = "傳送附件";
27 | "recent-rooms.leave.alert-body" = "確實要離開「%@」嗎?此動作無法復原。";
28 | "recent-rooms.leave.alert-title" = "離開聊天室";
29 | "recent-rooms.rooms.header" = "聊天室";
30 | "recent-rooms.pending-invitations.leave.alert-title" = "拒絕邀請?";
31 | "recent-rooms.pending-invitations.header" = "待處理的邀請";
32 | "recent-rooms.accessibility-label.new-message-badge" = "%u 則新訊息";
33 | // Room $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE
34 | "recent-rooms.accessibility-label.room" = "%@聊天室,%@ %@";
35 | // DM with $NAME, $LAST_ACTIVITY_TIMESTAMP $LAST_MESSAGE
36 | "recent-rooms.accessibility-label.dm" = "與%@私訊,%@ %@";
37 | "recent-rooms.new-message-placeholder" = "新訊息";
38 | "recent-rooms.accessibility-label.new-conversation" = "新對話";
39 | "recent-rooms.accessibility-label.settings" = "設定";
40 | "loading.cancel" = "取消";
41 | "loading.4" = "回復作業階段";
42 | "loading.3" = "登入中";
43 | "login.failure-back-to-login" = "返回登入頁";
44 | "login.register-not-yet-implemented" = "尚未提供創建新帳戶功能。";
45 | "login.open-registration-prompt" = "還沒有帳戶?";
46 | "login.sign-in" = "登入";
47 | "login.form.homeserver-optional-explanation" = "若您選用matrix.org則不必填寫家伺服器。";
48 | "login.form.homeserver" = "家伺服器";
49 | "login.form.password" = "密碼";
50 | "login.form.username" = "使用者名稱";
51 | "login.welcome-message" = "從登入帳戶開始。";
52 | "login.welcome-header" = "歡迎使用 ";
53 | "event.context-menu.remove" = "移除";
54 | "event.context-menu.edit" = "編輯";
55 | "event.context-menu.reply" = "回覆";
56 | "event.context-menu.add-reaction" = "加入反應";
57 | "event.edited" = "已編輯";
58 | "event.redaction.redact-other" = "%@ 移除了 %@ 的訊息";
59 | "event.redaction.redact-self" = "訊息已被 %@ 移除";
60 | "event.room-power-level.change-other" = "%@ 將權限等級從 %@ 更改為 %@";
61 | "event.room-power-level.change-self" = "%@ 將權限等級從 %@ 更改為 %@";
62 | "room-power-level.custom" = "自訂權限(%d)";
63 | "room-power-level.admin" = "管理員";
64 | "room-power-level.moderator" = "版主";
65 | "room-power-level.default" = "預設";
66 | "event.room-topic.change" = "%@ 將主題更改為「%@」";
67 | "event.room-name.change-name" = "%@ 將聊天室名稱從 %@ 更改為 %@";
68 | "event.room-name.set-name" = "%@ 將聊天室名稱設定為 %@";
69 | "event.reason" = "原由:%@";
70 | "event.room-member.unknown-state" = "未知的狀態事件:%@";
71 | "event.room-member.remove-avatar" = "%@ 移除了大頭照";
72 | "event.room-member.change-avatar" = "%@ 更新了大頭照";
73 | "event.room-member.set-avatar" = "%@ 設定了大頭照";
74 | "event.room-member.change-name" = "%@ 將顯示名稱更改為 %@";
75 | "new-conversation.done" = "完成";
76 | "new-conversation.edit" = "編輯";
77 | "new-conversation.cancel" = "取消";
78 | "new-conversation.alert-failed" = "開始聊天發生錯誤";
79 | "new-conversation.create-room" = "開始聊天";
80 | "new-conversation.public-room" = "公開聊天室";
81 | "new-conversation.room-name" = "聊天室名稱";
82 | "new-conversation.for-example" = "例如";
83 | "new-conversation.username-placeholder" = "Matrix ID";
84 | "new-conversation.title-room" = "新聊天室";
85 | "new-conversation.title-chat" = "新聊天對話";
86 | "settings.dismiss" = "完成";
87 | "settings.log-out" = "登出";
88 | "settings.app-icon" = "應用程式圖示";
89 | "settings.accent-color" = "強調色";
90 | "settings.title" = "設定";
91 | "reaction-picker.title" = "點擊顏文字以傳送您的回應。";
92 | "loading.2" = "打亂訊息";
93 | "loading.1" = "讓線條變成網狀";
94 |
--------------------------------------------------------------------------------
/Nio/Utility/Formatter.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum DateFormat: String {
4 | case yearMonthDay = "yyyy-MM-dd"
5 | }
6 |
7 | enum Formatter {
8 | // MARK: Relative Date Formatter
9 | static private func relativeDateTimeFormatter(dateTimeStyle: RelativeDateTimeFormatter.DateTimeStyle? = nil,
10 | unitsStyle: RelativeDateTimeFormatter.UnitsStyle? = nil,
11 | formattingContext: RelativeDateTimeFormatter.Context?,
12 | locale: Locale) -> RelativeDateTimeFormatter {
13 | let formatter = RelativeDateTimeFormatter()
14 | if let dateTimeStyle = dateTimeStyle {
15 | formatter.dateTimeStyle = dateTimeStyle
16 | }
17 | if let unitsStyle = unitsStyle {
18 | formatter.unitsStyle = unitsStyle
19 | }
20 | if let formattingContext = formattingContext {
21 | formatter.formattingContext = formattingContext
22 | }
23 | formatter.locale = locale
24 | return formatter
25 | }
26 |
27 | static func string(forRelativeDate relativeDate: Date,
28 | to otherDate: Date = Date(),
29 | context: RelativeDateTimeFormatter.Context? = nil,
30 | locale: Locale = .current) -> String {
31 | relativeDateTimeFormatter(dateTimeStyle: .named, unitsStyle: .full, formattingContext: context, locale: locale)
32 | .localizedString(for: relativeDate, relativeTo: otherDate)
33 | }
34 |
35 | // MARK: Date Formatter
36 | private static func dateFormatter(format: DateFormat? = nil,
37 | dateStyle: DateFormatter.Style? = nil,
38 | timeStyle: DateFormatter.Style? = nil,
39 | locale: Locale) -> DateFormatter {
40 | let formatter = DateFormatter()
41 | if let format = format {
42 | formatter.dateFormat = format.rawValue
43 | }
44 | if let dateStyle = dateStyle {
45 | formatter.dateStyle = dateStyle
46 | }
47 | if let timeStyle = timeStyle {
48 | formatter.timeStyle = timeStyle
49 | }
50 | formatter.locale = locale
51 | return formatter
52 | }
53 |
54 | static func string(for date: Date, format: DateFormat, locale: Locale = .current) -> String {
55 | let formatter = dateFormatter(format: format, locale: locale)
56 | return formatter.string(from: date)
57 | }
58 |
59 | static func string(for date: Date, dateStyle: DateFormatter.Style, locale: Locale = .current) -> String {
60 | string(for: date, dateStyle: dateStyle, timeStyle: .none, locale: locale)
61 | }
62 |
63 | static func string(for date: Date, timeStyle: DateFormatter.Style, locale: Locale = .current) -> String {
64 | string(for: date, dateStyle: .none, timeStyle: timeStyle, locale: locale)
65 | }
66 |
67 | static func string(for date: Date,
68 | dateStyle: DateFormatter.Style,
69 | timeStyle: DateFormatter.Style,
70 | locale: Locale = .current) -> String {
71 | let formatter = dateFormatter(dateStyle: dateStyle, timeStyle: timeStyle, locale: locale)
72 | return formatter.string(from: date)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Nio/Utility/GroupedEvents.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftMatrixSDK
3 |
4 | struct GroupBounds: OptionSet, Equatable, Hashable {
5 | let rawValue: Int
6 |
7 | static let before: Self = .init(rawValue: 1 << 0)
8 | static let after: Self = .init(rawValue: 1 << 1)
9 |
10 | static let both: Self = [.before, .after]
11 | }
12 |
13 | struct Bounded {
14 | var wrapped: Wrapped
15 | var bounds: GroupBounds
16 |
17 | init(_ wrapped: Wrapped, bounds: GroupBounds) {
18 | self.wrapped = wrapped
19 | self.bounds = bounds
20 | }
21 | }
22 |
23 | extension Bounded: Equatable where Wrapped: Equatable {}
24 |
25 | extension Bounded: Hashable where Wrapped: Hashable {}
26 |
27 | extension Bounded: Identifiable where Wrapped: Identifiable {
28 | // swiftlint:disable:next type_name
29 | typealias ID = Wrapped.ID
30 |
31 | var id: ID {
32 | wrapped.id
33 | }
34 | }
35 |
36 | extension Bounded where Wrapped == MXEvent {
37 | var event: MXEvent {
38 | get { wrapped }
39 | set { wrapped = newValue }
40 | }
41 | }
42 |
43 | struct EventGroup {
44 | let events: [MXEvent]
45 |
46 | init(_ events: [MXEvent] = []) {
47 | self.events = events
48 | }
49 | }
50 |
51 | struct EventGroups {
52 | var groups: [EventGroup]
53 |
54 | init(_ events: [MXEvent]) {
55 | self.groups = Self.groups(from: events)
56 | }
57 |
58 | // Collects events in groups of equal `.sender`:
59 | static func groups(from events: S) -> [EventGroup] where S: Sequence, S.Element == MXEvent {
60 | let iterator = events.makeIterator()
61 | let groupingIterator = GroupingIterator(iterator) { $0.sender }
62 | return IteratorSequence(groupingIterator).map { EventGroup($0) }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Nio/Utility/GroupingIterator.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// An iterator with a grouping closure that returns sub-sequences next element
4 | /// without removing it without advancing the iterator.
5 | struct GroupingIterator: IteratorProtocol where Iterator: IteratorProtocol {
6 | typealias Element = [Iterator.Element]
7 | typealias Closure = (Iterator.Element, Iterator.Element) -> Bool
8 |
9 | private var iterator: PeekableIterator
10 | private let closure: Closure
11 |
12 | /// Creates an grouping iterator, wrapping `iterator`.
13 | /// - Parameters:
14 | /// - iterator: the wrapped iterator
15 | /// - closure: A predicate that returns true if its second argument
16 | /// should be added to the same group as the first argument;
17 | /// otherwise, false.
18 | init(_ iterator: Iterator, by closure: @escaping Closure) {
19 | self.iterator = .init(iterator)
20 | self.closure = closure
21 | }
22 |
23 | mutating func next() -> Element? {
24 | guard var previous = iterator.next() else {
25 | return nil
26 | }
27 |
28 | var group: [Iterator.Element] = [previous]
29 |
30 | while let peek = iterator.peek() {
31 | guard closure(previous, peek) else {
32 | break
33 | }
34 |
35 | // The forced unwrap is safe here, due to having peeked above:
36 | let element = iterator.next()!
37 |
38 | group.append(element)
39 |
40 | previous = element
41 | }
42 |
43 | return group
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Nio/Utility/MXURL.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | struct MXURL {
5 | var mxContentURI: URL
6 |
7 | init?(mxContentURI: String) {
8 | guard let uri = URL(string: mxContentURI) else {
9 | return nil
10 | }
11 | self.mxContentURI = uri
12 | }
13 |
14 | func contentURL(on homeserver: URL) -> URL? {
15 | var components = URLComponents()
16 | components.scheme = "https"
17 | components.host = homeserver.host
18 | guard let contentHost = mxContentURI.host else { return nil }
19 | components.path = "/_matrix/media/r0/download/\(contentHost)/\(mxContentURI.lastPathComponent)"
20 | return components.url
21 | }
22 | }
23 |
24 | extension MXURL {
25 | static var nioIconURL: URL {
26 | Self.nioIcon.contentURL(on: URL(string: "https://matrix.org")!)!
27 | }
28 |
29 | static var nioIcon: MXURL {
30 | MXURL(mxContentURI: "mxc://matrix.org/rdElwkPTTrdZljUuKwkSEMqV")!
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Nio/Utility/PeekableIterator.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// An iterator with a `peek()` that returns the next element
4 | /// without removing it without advancing the iterator.
5 | struct PeekableIterator: IteratorProtocol where Iterator: IteratorProtocol {
6 | typealias Element = Iterator.Element
7 |
8 | private var iterator: Iterator
9 | private var peeked: Element?
10 |
11 | /// Creates a peekable iterator, wrapping `iterator`.
12 | /// - Parameter iterator: the wrapped iterator
13 | init(_ iterator: Iterator) {
14 | self.iterator = iterator
15 | }
16 |
17 | /// Advances the iterator and returns the next value.
18 | mutating func next() -> Element? {
19 | guard let peeked = peeked else {
20 | return iterator.next()
21 | }
22 | defer { self.peeked = nil }
23 | return peeked
24 | }
25 |
26 | /// Returns the `next()` value without advancing the iterator.
27 | mutating func peek() -> Element? {
28 | if peeked == nil {
29 | peeked = iterator.next()
30 | }
31 | return peeked
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Nio/Utility/TypedEvents.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | protocol TypeableEvent {
5 | var type: String { get }
6 | var sender: String { get }
7 | }
8 |
9 | /// An event and its type
10 | struct TypedEvent where Event: TypeableEvent {
11 | let type: MXEventType
12 | let event: Event
13 | }
14 |
15 | extension TypedEvent: Equatable where Event: Equatable {
16 | static func == (lhs: TypedEvent, rhs: TypedEvent) -> Bool {
17 | guard lhs.type == rhs.type else {
18 | return false
19 | }
20 |
21 | guard lhs.event == rhs.event else {
22 | return false
23 | }
24 |
25 | return true
26 | }
27 | }
28 |
29 | /// A non-empty group of homogeneous events
30 | struct TypedEventGroup where Event: TypeableEvent {
31 | let type: MXEventType
32 | let events: [Event]
33 |
34 | init(type: MXEventType, events: [TypedEvent]) {
35 | let events = events.map { $0.event }
36 | self.init(type: type, events: events)
37 | }
38 |
39 | init(type: MXEventType, events: [Event]) {
40 | assert(!events.isEmpty)
41 | assert(events.allSatisfy { event in
42 | MXEventType(identifier: event.type) == type
43 | })
44 |
45 | self.type = type
46 | self.events = events
47 | }
48 | }
49 |
50 | extension TypedEventGroup: Equatable where Event: Equatable {
51 | static func == (lhs: TypedEventGroup, rhs: TypedEventGroup) -> Bool {
52 | lhs.events == rhs.events
53 | }
54 | }
55 |
56 | /// A collection of groups of events of equal type.
57 | struct TypedEventGroups: Collection where Event: TypeableEvent {
58 | typealias Index = Int
59 |
60 | var startIndex: Int {
61 | 0
62 | }
63 |
64 | var endIndex: Int {
65 | groups.count
66 | }
67 |
68 | let groups: [TypedEventGroup]
69 |
70 | init(events: S) where S: Sequence, S.Element == Event {
71 | self.init(groups: Self.grouped(events: events))
72 | }
73 |
74 | init(groups: [TypedEventGroup]) {
75 | self.groups = groups
76 | }
77 |
78 | private static func grouped(events: S) -> [TypedEventGroup] where S: Sequence, S.Element == Event {
79 | let typedEvents: LazyMapSequence> = events.lazy.map { event in
80 | let type = MXEventType(identifier: event.type)
81 | return TypedEvent(type: type, event: event)
82 | }
83 | let iterator = typedEvents.makeIterator()
84 | let groupingIterator = GroupingIterator(iterator) { lhs, rhs in
85 | // Split the events into groups of consecutive events of same type:
86 | guard lhs.type == rhs.type else {
87 | return false
88 | }
89 | switch lhs.type {
90 | // Perform additional specialized grouping for message events:
91 | case .roomMessage, .roomMessageFeedback:
92 | // Groups into consecutive events of same sender:
93 | return lhs.event.sender == rhs.event.sender
94 | case _:
95 | return true
96 | }
97 | }
98 | let groups = IteratorSequence(groupingIterator)
99 | return groups.compactMap { events in
100 | guard let event = events.first else {
101 | return nil
102 | }
103 | return TypedEventGroup(type: event.type, events: events)
104 | }
105 | }
106 |
107 | subscript(position: Int) -> TypedEventGroup {
108 | groups[position]
109 | }
110 |
111 | func index(after index: Int) -> Int {
112 | index + 1
113 | }
114 | }
115 |
116 | extension TypedEventGroups: Equatable where Event: Equatable {
117 | static func == (lhs: TypedEventGroups, rhs: TypedEventGroups) -> Bool {
118 | lhs.groups == rhs.groups
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/NioKit/Configuration/Configuration.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | class Configuration {
5 | static func setupMatrixSDKSettings() {
6 | let sdkOptions = MXSDKOptions.sharedInstance()
7 |
8 | // sdkOptions.applicationGroupIdentifier = ""
9 |
10 | // Enable e2e encryption for newly created MXSession
11 | sdkOptions.enableCryptoWhenStartingMXSession = true
12 | sdkOptions.computeE2ERoomSummaryTrust = true
13 |
14 | // Use UIKit BackgroundTask for handling background tasks in the SDK
15 | // sdkOptions.backgroundModeHandler = MXUIKitBackgroundModeHandler()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/NioKit/Extensions/MX+Identifiable.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import MatrixSDK
3 |
4 | extension MXPublicRoom: Identifiable {}
5 | extension MXRoom: Identifiable {}
6 | extension MXEvent: Identifiable {}
7 |
--------------------------------------------------------------------------------
/NioKit/Extensions/MXClient+Publisher.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Combine
3 | import MatrixSDK
4 |
5 | extension MXRestClient {
6 | public func nio_publicRooms(onServer: String? = nil, limit: UInt? = nil) -> AnyPublisher {
7 | Future { promise in
8 | self.publicRooms(onServer: onServer, limit: limit) { response in
9 | switch response {
10 | case .failure(let error):
11 | promise(.failure(error))
12 | case .success(let publicRoomsResponse):
13 | promise(.success(publicRoomsResponse))
14 | @unknown default:
15 | fatalError("Unexpected Matrix response: \(response)")
16 | }
17 | }
18 | }.eraseToAnyPublisher()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NioKit/Extensions/MXCredentials+Keychain.swift:
--------------------------------------------------------------------------------
1 | import MatrixSDK
2 | import KeychainAccess
3 |
4 | extension MXCredentials {
5 | public func save(to keychain: Keychain) {
6 | guard
7 | let homeserver = self.homeServer,
8 | let userId = self.userId,
9 | let accessToken = self.accessToken,
10 | let deviceId = self.deviceId
11 | else {
12 | return
13 | }
14 | keychain["homeserver"] = homeserver
15 | keychain["userId"] = userId
16 | keychain["accessToken"] = accessToken
17 | keychain["deviceId"] = deviceId
18 | }
19 |
20 | public func clear(from keychain: Keychain) {
21 | keychain["homeserver"] = nil
22 | keychain["userId"] = nil
23 | keychain["accessToken"] = nil
24 | keychain["deviceId"] = nil
25 | }
26 |
27 | public static func from(_ keychain: Keychain) -> MXCredentials? {
28 | guard
29 | let homeserver = keychain["homeserver"],
30 | let userId = keychain["userId"],
31 | let accessToken = keychain["accessToken"],
32 | let deviceId = keychain["deviceId"]
33 | else {
34 | return nil
35 | }
36 | let credentials = MXCredentials(
37 | homeServer: homeserver,
38 | userId: userId,
39 | accessToken: accessToken
40 | )
41 | credentials.deviceId = deviceId
42 | return credentials
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/NioKit/Extensions/MXEvent+Extensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | extension MXEvent {
5 | public var timestamp: Date {
6 | Date(timeIntervalSince1970: TimeInterval(self.originServerTs / 1000))
7 | }
8 |
9 | public func content(valueFor key: String) -> T? {
10 | if let value = self.content?[key] as? T {
11 | return value
12 | }
13 | return nil
14 | }
15 |
16 | public func prevContent(valueFor key: String) -> T? {
17 | if let value = self.unsignedData?.prevContent?[key] as? T {
18 | return value
19 | }
20 | return nil
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/NioKit/Extensions/UXKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UXKit.swift
3 | // Nio
4 | //
5 | // Created by Helge Heß on 05.03.21.
6 | // Copyright © 2021 Kilian Koeltzsch. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import AppKit
11 |
12 | public typealias UXColor = NSColor
13 | public typealias UXImage = NSImage
14 | public typealias UXEdgeInsets = NSEdgeInsets
15 | public typealias UXFont = NSFont
16 |
17 | public enum UXFakeTraitCollection {
18 | case current
19 | }
20 |
21 | public extension NSColor {
22 |
23 | @inlinable
24 | func resolvedColor(with fakeTraitCollection: UXFakeTraitCollection)
25 | -> UXColor
26 | {
27 | return self
28 | }
29 | }
30 |
31 | #if canImport(SwiftUI)
32 | import SwiftUI
33 |
34 | public enum UXFakeDisplayMode {
35 | case inline, automatic, large
36 | }
37 | public enum UXFakeAutocapitalizationMode {
38 | case none
39 | }
40 |
41 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
42 | public extension View {
43 |
44 | @inlinable
45 | func navigationBarTitle(
46 | _ title: S, displayMode: UXFakeDisplayMode = .inline
47 | ) -> some View
48 | {
49 | self.navigationTitle(title)
50 | }
51 |
52 | @inlinable
53 | func autocapitalization(_ mode: UXFakeAutocapitalizationMode) -> Self {
54 | return self
55 | }
56 | }
57 | #endif
58 | #elseif canImport(UIKit)
59 | import UIKit
60 |
61 | public typealias UXColor = UIColor
62 | public typealias UXImage = UIImage
63 | public typealias UXEdgeInsets = UIEdgeInsets
64 | public typealias UXFont = UIFont
65 |
66 | #if canImport(SwiftUI)
67 | import SwiftUI
68 |
69 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
70 | public extension View {
71 | }
72 | #endif
73 | #else
74 | #error("GNUstep not yet supported, sorry!")
75 | #endif
76 |
--------------------------------------------------------------------------------
/NioKit/Extensions/UserDefaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults.swift
3 | // NioKit
4 | //
5 | // Created by Stefan Hofman on 05/09/2020.
6 | // Copyright © 2020 Kilian Koeltzsch. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension UserDefaults {
12 | private static let appGroup: String = {
13 | guard let group = Bundle.main.infoDictionary?["AppGroup"] as? String else {
14 | fatalError("Missing 'AppGroup' key in Info.plist!")
15 | }
16 | return group
17 | }()
18 | #if os(macOS)
19 | private static let teamIdentifierPrefix = Bundle.main
20 | .object(forInfoDictionaryKey: "TeamIdentifierPrefix") as? String ?? ""
21 |
22 | private static let suiteName = teamIdentifierPrefix + appGroup
23 | #else // iOS
24 | private static let suiteName = "group." + appGroup
25 | #endif
26 |
27 | static let group = UserDefaults(suiteName: suiteName)!
28 | }
29 |
--------------------------------------------------------------------------------
/NioKit/Models/Custom Events/CustomEvent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol CustomEvent {
4 | func encodeContent() throws -> [String: Any]
5 | }
6 |
--------------------------------------------------------------------------------
/NioKit/Models/Custom Events/EditEvent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | struct EditEvent {
5 | let eventId: String
6 | let text: String
7 | }
8 |
9 | extension EditEvent: CustomEvent {
10 | func encodeContent() throws -> [String: Any] {
11 | [
12 | "body": "*" + text,
13 | "m.new_content": [
14 | "body": text,
15 | "msgtype": kMXMessageTypeText
16 | ],
17 | "m.relates_to": [
18 | "event_id": eventId,
19 | "rel_type": MXEventRelationTypeReplace
20 | ],
21 | "msgtype": kMXMessageTypeText
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/NioKit/Models/Custom Events/ReactionEvent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | struct ReactionEvent {
5 | let eventId: String
6 | let key: String
7 | let relType = MXEventRelationTypeAnnotation
8 | }
9 |
10 | extension ReactionEvent: CustomEvent {
11 | func encodeContent() throws -> [String: Any] {
12 | [
13 | "m.relates_to": [
14 | "event_id": eventId,
15 | "key": key,
16 | "rel_type": relType
17 | ]
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NioKit/Models/NIORoomSummary.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MatrixSDK
3 |
4 | @dynamicMemberLookup
5 | public class NIORoomSummary: ObservableObject {
6 | internal var summary: MXRoomSummary
7 |
8 | public var lastMessageDate: Date {
9 | let timestamp = Double(summary.lastMessage.originServerTs)
10 | return Date(timeIntervalSince1970: timestamp / 1000)
11 | }
12 |
13 | public init(_ summary: MXRoomSummary) {
14 | self.summary = summary
15 | }
16 |
17 | public subscript(dynamicMember keyPath: KeyPath) -> T {
18 | summary[keyPath: keyPath]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NioKit/Models/Reaction.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Reaction: Identifiable {
4 | public var id: String
5 | public let sender: String
6 | public let timestamp: Date
7 | public let reaction: String
8 |
9 | public init(
10 | id: String,
11 | sender: String,
12 | timestamp: Date,
13 | reaction: String
14 | ) {
15 | self.id = id
16 | self.sender = sender
17 | self.timestamp = timestamp
18 | self.reaction = reaction
19 | }
20 | }
21 |
22 | public struct ReactionGroup: Identifiable {
23 | public let reaction: String
24 | public let count: Int
25 | public let reactions: [Reaction]
26 |
27 | public var id: String {
28 | self.reaction
29 | }
30 |
31 | public init(reaction: String, count: Int, reactions: [Reaction]) {
32 | self.reaction = reaction
33 | self.count = count
34 | self.reactions = reactions
35 | }
36 |
37 | public func containsReaction(from sender: String) -> Bool {
38 | self.reactions.contains { $0.sender == sender }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/NioKitTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NioKitTests/NioKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | @testable import NioKit
4 |
5 | class NioKitTests: XCTestCase {
6 | override func setUpWithError() throws {
7 | // Put setup code here. This method is called before the invocation of each test method in the class.
8 | }
9 |
10 | override func tearDownWithError() throws {
11 | // Put teardown code here. This method is called after the invocation of each test method in the class.
12 | }
13 |
14 | func testExample() throws {
15 | // This is an example of a functional test case.
16 | // Use XCTAssert and related functions to verify your tests produce the correct results.
17 | }
18 |
19 | func testPerformanceExample() throws {
20 | // This is an example of a performance test case.
21 | self.measure {
22 | // Put the code you want to measure the time of here.
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/NioShareExtension/GetURL.js:
--------------------------------------------------------------------------------
1 | var GetURL = function() {};
2 | GetURL.prototype = {
3 | run: function(arguments) {
4 | arguments.completionFunction({"url": document.URL});
5 | }
6 | };
7 | var ExtensionPreprocessingJS = new GetURL;
8 |
--------------------------------------------------------------------------------
/NioShareExtension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AppGroup
6 | $(APPGROUP)
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | NioShareExtension
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | DevelopmentTeam
26 | $(DEVELOPMENT_TEAM)
27 | NSExtension
28 |
29 | NSExtensionAttributes
30 |
31 | IntentsSupported
32 |
33 | NSExtensionActivationRule
34 |
35 | NSExtensionActivationSupportsWebURLWithMaxCount
36 | 1
37 |
38 | NSExtensionJavaScriptPreprocessingFile
39 | GetURL
40 |
41 | NSExtensionPointIdentifier
42 | com.apple.share-services
43 | NSExtensionPrincipalClass
44 | ShareNavigationController
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/NioShareExtension/NioShareExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.$(APPGROUP)
10 |
11 | com.apple.security.network.client
12 |
13 | keychain-access-groups
14 |
15 | $(AppIdentifierPrefix)nio.keychain
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/NioShareExtension/ShareContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import NioKit
3 | import UIKit
4 |
5 | struct ShareContentView: View {
6 | @State var parentView: ShareNavigationController
7 | @State var showConfirm = false
8 | @State var selectedRoom: RoomItem?
9 |
10 | var rooms: [RoomItem]?
11 |
12 | public init(parentView: ShareNavigationController) {
13 | _parentView = State(initialValue: parentView)
14 | let data = UserDefaults.group.data(forKey: "roomList")!
15 | do {
16 | let decoder = JSONDecoder()
17 | let temp = try decoder.decode([RoomItem].self, from: data)
18 | self.rooms = temp
19 | self.rooms?.sort(by: { $0.messageDate > $1.messageDate })
20 | } catch {
21 | print("An error occured: \(error)")
22 | }
23 | }
24 |
25 | var body: some View {
26 | NavigationView {
27 | VStack {
28 | List(rooms!, id: \.self) { room in
29 | Button(action: {
30 | self.selectedRoom = room
31 | self.showConfirm.toggle()
32 | }, label: {
33 | Text(room.displayName)
34 | })
35 | }
36 | .padding(.bottom, 20.0)
37 | .listStyle(GroupedListStyle())
38 | .navigationBarTitle("Nio")
39 | .alert(isPresented: $showConfirm) {
40 | Alert(
41 | title: Text("Send to " + self.selectedRoom!.displayName),
42 | primaryButton: .default(
43 | Text("Send"),
44 | action: {
45 | self.parentView.didSelectPost(roomID: self.selectedRoom!.roomId)
46 | }),
47 | secondaryButton: .cancel())
48 | }
49 | // This is ugly, but otherwise the last results are too far down. Update if you know
50 | // a better way.
51 | Text("")
52 | Text("")
53 | Text("")
54 | }
55 | }
56 | }
57 | }
58 |
59 | struct ShareContentView_Previews: PreviewProvider {
60 | static var previews: some View {
61 | /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/NioShareExtension/ShareViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Social
3 | import MobileCoreServices
4 | import SwiftUI
5 | import NioKit
6 | import MatrixSDK
7 |
8 | @objc(ShareNavigationController)
9 | class ShareNavigationController: UIViewController {
10 |
11 | let store: AccountStore = AccountStore.init()
12 |
13 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
14 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
15 | self.modalPresentationStyle = UIModalPresentationStyle.fullScreen
16 | let childView = UIHostingController(rootView: ShareContentView(parentView: self))
17 | addChild(childView)
18 | childView.view.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
19 | view.addSubview(childView.view)
20 | childView.didMove(toParent: self)
21 | }
22 |
23 | func didSelectPost(roomID: String) {
24 | switch store.loginState {
25 | case .loggedIn(_):
26 | let propertyList = String(kUTTypePropertyList)
27 | let rooms = store.rooms
28 | if let item = extensionContext?.inputItems.first as? NSExtensionItem,
29 | let itemProvider = item.attachments?.first,
30 | itemProvider.hasItemConformingToTypeIdentifier(propertyList) {
31 | itemProvider.loadItem(forTypeIdentifier: propertyList, options: nil) { (item, _) in
32 | guard let dictionary = item as? NSDictionary,
33 | let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary,
34 | let url = results["url"] as? String else {
35 | return
36 | }
37 | for room in rooms where room.summary.roomId == roomID {
38 | room.send(text: url)
39 | }
40 | self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
41 | }
42 | }
43 | case .loggedOut:
44 | didSelectCancel()
45 | case .authenticating:
46 | let alert = UIAlertController(title: nil, message: "Sending...", preferredStyle: .alert)
47 |
48 | let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
49 | loadingIndicator.hidesWhenStopped = true
50 | loadingIndicator.style = UIActivityIndicatorView.Style.medium
51 | loadingIndicator.startAnimating()
52 |
53 | alert.view.addSubview(loadingIndicator)
54 | present(alert, animated: true, completion: nil)
55 | case .failure(_):
56 | didSelectCancel()
57 | }
58 | }
59 |
60 | enum ShareViewControllerError: Error {
61 | case assertionError(description: String)
62 | case unsupportedMedia
63 | case notRegistered
64 | case obsoleteShare
65 | }
66 |
67 | func didSelectCancel() {
68 | self.dismiss(animated: true) {
69 | self.extensionContext!.cancelRequest(withError: ShareViewControllerError.obsoleteShare)
70 | }
71 | }
72 |
73 | @available(*, unavailable)
74 | required init?(coder aDecoder: NSCoder) {
75 | super.init(coder: aDecoder)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/NioTests/EventCollectionTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import MatrixSDK
3 |
4 | @testable import Nio
5 | @testable import NioKit
6 |
7 | // swiftlint:disable identifier_name
8 |
9 | class EventCollectionTests: XCTestCase {
10 | func testReadConnectedEdges() {
11 | let m = kMXEventTypeStringRoomMessage
12 |
13 | let m1 = MockEvent(sender: "A", type: m, timestamp: 0, isRedacted: false)
14 | let m2 = MockEvent(sender: "A", type: m, timestamp: 0, isRedacted: false)
15 | let m3 = MockEvent(sender: "A", type: m, timestamp: 0, isRedacted: false)
16 |
17 | let events = [m1, m2, m3]
18 |
19 | XCTAssertEqual(EventCollection(events).connectedEdges(of: m1), [.bottomEdge])
20 | XCTAssertEqual(EventCollection(events).connectedEdges(of: m2), [.topEdge, .bottomEdge])
21 | XCTAssertEqual(EventCollection(events).connectedEdges(of: m3), [.topEdge])
22 |
23 | // FIXME: This obviously needs to cover all cases.
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/NioTests/GroupingIteratorTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | @testable import Nio
4 |
5 | class GroupingIteratorTests: XCTestCase {
6 | func testGroupingIteratorShouldSplitElementsByClosure() {
7 | let elements: [Int] = [0, 1, 3, 2, 3]
8 | let iterator = elements.makeIterator()
9 | let groupingIterator: GroupingIterator = .init(iterator) {
10 | // Group elements by their even/odd-ness:
11 | ($0 % 2) == ($1 % 2)
12 | }
13 |
14 | let expected: [[Int]] = [
15 | [0],
16 | [1, 3],
17 | [2],
18 | [3]
19 | ]
20 | let actual = Array(IteratorSequence(groupingIterator))
21 | XCTAssertEqual(actual, expected)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NioTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NioTests/Mocks/MockEvent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import class MatrixSDK.MXEvent
3 |
4 | class MockEvent: MXEvent {
5 | init(sender: String, type: String, timestamp: UInt64, isRedacted: Bool) {
6 | self._type = type
7 | self.isRedacted = isRedacted
8 | super.init()
9 | self.sender = sender
10 | self.originServerTs = 1000 * timestamp
11 | }
12 |
13 | // swiftlint:disable:next identifier_name
14 | var _type: String
15 | override var type: String! {
16 | _type
17 | }
18 |
19 | var isRedacted: Bool
20 | override func isRedactedEvent() -> Bool {
21 | isRedacted
22 | }
23 |
24 | required init?(coder: NSCoder) {
25 | fatalError("init(coder:) has not been implemented")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/NioTests/PeekableIteratorTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | @testable import Nio
4 |
5 | class PeekableIteratorTests: XCTestCase {
6 | func testNextShouldRemoveAndReturnNextElement() {
7 | let elements = [0, 1, 2, 3, 4]
8 | let iterator = elements.makeIterator()
9 | var peekableIterator = PeekableIterator(iterator)
10 |
11 | let actual0 = peekableIterator.next()
12 | let expected0 = 0
13 | XCTAssertEqual(actual0, expected0)
14 |
15 | let actual1 = peekableIterator.next()
16 | let expected1 = 1
17 | XCTAssertEqual(actual1, expected1)
18 | }
19 |
20 | func testPeekShouldReturnButNotRemoveNextElement() {
21 | let elements = [0, 1, 2, 3, 4]
22 | let iterator = elements.makeIterator()
23 | var peekableIterator = PeekableIterator(iterator)
24 |
25 | // Calling peek should return the next element:
26 | let actual0 = peekableIterator.peek()
27 | let expected0 = 0
28 | XCTAssertEqual(actual0, expected0)
29 |
30 | // Calling peek again should return the same element:
31 | let actual1 = peekableIterator.peek()
32 | let expected1 = 0
33 | XCTAssertEqual(actual1, expected1)
34 | }
35 |
36 | func testNextAfterPeekShouldRemoveAndReturnPeekedElement() {
37 | let elements = [0, 1, 2, 3, 4]
38 | let iterator = elements.makeIterator()
39 | var peekableIterator = PeekableIterator(iterator)
40 |
41 | // Calling peek should return the next element:
42 | let actual0 = peekableIterator.peek()
43 | let expected0 = 0
44 | XCTAssertEqual(actual0, expected0)
45 |
46 | // Calling next should return the peeked element:
47 | let actual1 = peekableIterator.next()
48 | let expected1 = 0
49 | XCTAssertEqual(actual1, expected1)
50 | }
51 |
52 | func testPeekAfterNextShouldRemoveAndReturnPeekedElement() {
53 | let elements = [0, 1, 2, 3, 4]
54 | let iterator = elements.makeIterator()
55 | var peekableIterator = PeekableIterator(iterator)
56 |
57 | // Calling next should return the next element:
58 | let actual0 = peekableIterator.next()
59 | let expected0 = 0
60 | XCTAssertEqual(actual0, expected0)
61 |
62 | // Calling peek should return the next element:
63 | let actual1 = peekableIterator.peek()
64 | let expected1 = 1
65 | XCTAssertEqual(actual1, expected1)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/NioTests/TypedEventsTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import MatrixSDK
3 |
4 | @testable import Nio
5 |
6 | class TypedEventsTests: XCTestCase {
7 | struct Event: TypeableEvent, Equatable {
8 | let id: Int
9 | let type: String
10 | let sender: String
11 |
12 | public init(id: Int, type: MXEventType, sender: String) {
13 | self.id = id
14 | self.type = type.identifier
15 | self.sender = sender
16 | }
17 | }
18 |
19 | func testGroupHomogeneousMessages() {
20 | let events: [Event] = [
21 | Event(id: 0, type: .roomMessage, sender: "Alice"),
22 | Event(id: 1, type: .roomMessage, sender: "Alice"),
23 | Event(id: 2, type: .roomMessage, sender: "Bob"),
24 | Event(id: 3, type: .roomMessage, sender: "Eve"),
25 | Event(id: 4, type: .roomMessage, sender: "Eve"),
26 | Event(id: 5, type: .roomMessage, sender: "Bob")
27 | ]
28 |
29 | let actual = TypedEventGroups(events: events)
30 | let expected: TypedEventGroups = TypedEventGroups(groups: [
31 | TypedEventGroup(type: .roomMessage, events: [
32 | Event(id: 0, type: .roomMessage, sender: "Alice"),
33 | Event(id: 1, type: .roomMessage, sender: "Alice")
34 | ]),
35 | TypedEventGroup(type: .roomMessage, events: [
36 | Event(id: 2, type: .roomMessage, sender: "Bob")
37 | ]),
38 | TypedEventGroup(type: .roomMessage, events: [
39 | Event(id: 3, type: .roomMessage, sender: "Eve"),
40 | Event(id: 4, type: .roomMessage, sender: "Eve")
41 | ]),
42 | TypedEventGroup(type: .roomMessage, events: [
43 | Event(id: 5, type: .roomMessage, sender: "Bob")
44 | ])
45 | ])
46 |
47 | XCTAssertEqual(actual, expected)
48 | }
49 |
50 | func testGroupHeterogeneousEvents() {
51 | let events: [Event] = [
52 | Event(id: 0, type: .roomMessage, sender: "Alice"),
53 | Event(id: 1, type: .roomMessage, sender: "Alice"),
54 | Event(id: 2, type: .presence, sender: "Alice"),
55 | Event(id: 3, type: .roomMessage, sender: "Eve"),
56 | Event(id: 4, type: .roomMessage, sender: "Eve"),
57 | Event(id: 5, type: .roomMessage, sender: "Bob")
58 | ]
59 |
60 | let actual = TypedEventGroups(events: events)
61 | let expected: TypedEventGroups = TypedEventGroups(groups: [
62 | TypedEventGroup(type: .roomMessage, events: [
63 | Event(id: 0, type: .roomMessage, sender: "Alice"),
64 | Event(id: 1, type: .roomMessage, sender: "Alice")
65 | ]),
66 | TypedEventGroup(type: .presence, events: [
67 | Event(id: 2, type: .presence, sender: "Alice")
68 | ]),
69 | TypedEventGroup(type: .roomMessage, events: [
70 | Event(id: 3, type: .roomMessage, sender: "Eve"),
71 | Event(id: 4, type: .roomMessage, sender: "Eve")
72 | ]),
73 | TypedEventGroup(type: .roomMessage, events: [
74 | Event(id: 5, type: .roomMessage, sender: "Bob")
75 | ])
76 | ])
77 |
78 | XCTAssertEqual(actual, expected)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 💬 Nio
2 |
4 |
5 |
6 | [](https://matrix.to/#/#niochat:matrix.org)
7 | [](https://liberapay.com/nio/)
8 |
9 | Nio is an upcoming [Matrix](https://matrix.org) client for iOS.
10 |
11 | For the time being this project is still very much a work in progress. For updates, please check by in our matrix room → [#niochat:matrix.org](https://matrix.to/#/#niochat:matrix.org).
12 |
13 | ~~Want to give it a spin? Join the public [TestFlight Beta](https://testflight.apple.com/join/KlXr3kKz).~~
14 |
15 | **This project is currently on hiatus and not under active development. Please use one of the other [fantastic iOS Matrix clients](https://matrix.org/ecosystem/clients/) :)**
16 |
17 | 
18 |
19 | ### Getting Started
20 |
21 | Since Nio uses Swift Package Manager all you need to do is clone the project, open it in Xcode and hit ⌘ R to run the app on your simulator or device.
22 |
23 | ### Translations
24 |
25 | If you can help translate Nio into other languages your help would be much appreciated! Translation files are available on [translate.riot.im](https://translate.riot.im/engage/nio).
26 |
27 | [](https://translate.riot.im/engage/nio/?utm_source=widget)
28 |
29 | ### Funding
30 |
31 | As with any project, development costs a lot of time and money. Nio has no intention whatsoever of ever becoming a paid application or to support itself through *icky* means like ads or selling user data. But if you feel so inclined to support the project financially, we'd be forever grateful.
32 |
33 | You can either use [Liberapay](https://liberapay.com/nio/) for "traditional" payment methods or donate some [Nano](https://nano.org) to `nano_1dr4k1p63sas5qf9wyepqnuxwpjgik8z1f3nmk1gs8dpopaz97er6rq4tojm`.
34 |
35 | | |
36 | |------|
37 | | Nano |
38 |
39 | Other cryptocurrencies are of course just as welcome, just ask in the chat.
40 |
41 | Thank you! ❤️
42 |
43 | ### Similar Apps
44 |
45 | You might also have fun checking out the following applications.
46 |
47 | - [FluffyChat](https://fluffychat.im/), arguably the cutest mobile Matrix client.
48 | - [Ditto Chat](https://www.dittochat.org/), cross-platform (based on ReactNative) and just as purple 💜
49 |
50 | ### Why "Nio"?
51 |
52 | *Nio* is short for *Niobe* (albeit pronounced differently), captain of the ship *Logos* in the [Matrix trilogy](https://en.wikipedia.org/wiki/The_Matrix_(franchise)). Additionally it's a homonym of *Neo*, because there's definitely not enough Matrix projects jumping on that pun train already.
53 |
54 | The abbreviation NIO is also commonly used to refer to the term *non-blocking IO*. I find this entertaining for a messaging client, which is essentially a human IO interface.
55 |
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/Default.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/Default.sketch
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/Icon.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/Rounded_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/Rounded_1024.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/Rounded_500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/Rounded_500.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_20pt.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_20pt@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_20pt@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_29pt.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_29pt@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_29pt@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_40pt.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_40pt@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_40pt@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_60pt@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_60pt@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_76pt.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_76pt@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Default/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Default/icon_83.5@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Dark/Six Colors Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Dark/Six Colors Dark.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Dark/Six Colors Dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Dark/Six Colors Dark@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Dark/Six Colors Dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Dark/Six Colors Dark@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Dark/Six Colors.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Dark/Six Colors.sketch
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Light/Six Colors Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Light/Six Colors Light.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Light/Six Colors Light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Light/Six Colors Light@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Six Colors Light/Six Colors Light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Six Colors Light/Six Colors Light@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Sketch/Sketch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Sketch/Sketch.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Sketch/Sketch@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Sketch/Sketch@2x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/Sketch/Sketch@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/AppIcon/Sketch/Sketch@3x.png
--------------------------------------------------------------------------------
/Resources/AppIcon/attribution.txt:
--------------------------------------------------------------------------------
1 | chat by mikicon from the Noun Project
--------------------------------------------------------------------------------
/Resources/Application.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/Application.sketch
--------------------------------------------------------------------------------
/Resources/Base/Chat.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/Base/Chat.pdf
--------------------------------------------------------------------------------
/Resources/Base/Chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/Base/Chat.png
--------------------------------------------------------------------------------
/Resources/Base/Chat.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niochat/nio/35342d62d37fc3d176f119a54bb75d26fc8e446e/Resources/Base/Chat.sketch
--------------------------------------------------------------------------------
/Resources/Base/Chat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Chat
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Resources/Base/attribution.txt:
--------------------------------------------------------------------------------
1 | chat by mikicon from the Noun Project
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | # Uncomment the line if you want fastlane to automatically update itself
14 | # update_fastlane
15 |
16 | lane :beta do
17 | ensure_git_status_clean
18 | # increment_build_number
19 | build_app(project: "Nio.xcodeproj", scheme: "Nio")
20 | upload_to_testflight(
21 | skip_waiting_for_build_processing: true
22 | )
23 | end
24 |
25 | after_all do |lane|
26 | notification(message: "finished lane '#{lane}'", open: "http://appstoreconnect.apple.com")
27 | end
28 |
--------------------------------------------------------------------------------
/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ================
3 | # Installation
4 |
5 | Make sure you have the latest version of the Xcode command line tools installed:
6 |
7 | ```
8 | xcode-select --install
9 | ```
10 |
11 | Install _fastlane_ using
12 | ```
13 | [sudo] gem install fastlane -NV
14 | ```
15 | or alternatively using `brew cask install fastlane`
16 |
17 | # Available Actions
18 | ### beta
19 | ```
20 | fastlane beta
21 | ```
22 |
23 |
24 | ----
25 |
26 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
27 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
28 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
29 |
--------------------------------------------------------------------------------
/swiftgen.yml:
--------------------------------------------------------------------------------
1 | strings:
2 | inputs: "Nio/Supporting Files/en.lproj"
3 | filter: .+\.strings$
4 | outputs:
5 | - templateName: structured-swift4
6 | output: Nio/Generated/Strings.swift
7 |
8 | xcassets:
9 | inputs: "Nio/Assets.xcassets"
10 | filter: .+\.xcassets$
11 | outputs:
12 | - templateName: swift4
13 | output: Nio/Generated/Assets.swift
14 |
--------------------------------------------------------------------------------