Skip to content

Commit

Permalink
Merge pull request #56 from Athlon007/dev
Browse files Browse the repository at this point in the history
Update 0.2
  • Loading branch information
Athlon007 committed Jul 24, 2023
2 parents 76003e2 + 353b16b commit 3960929
Show file tree
Hide file tree
Showing 76 changed files with 4,113 additions and 1,875 deletions.
4 changes: 3 additions & 1 deletion .github/ISSUE_TEMPLATE/bug-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1.
1.

**Expected behavior**
A clear and concise description of what you expected to happen.
Expand All @@ -22,6 +22,8 @@ If applicable, add screenshots to help explain your problem.

**Information:**
- OS Version: [e.g. 13.1]
- App Version: [e.g. 0.1]
- Lemmy Instance: [e.g. lemmy.ml]

**Additional context**
Add any other context about the problem here.
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
# Changelog

## 0.2 (2023-07-24)

*Note: You will have to log in again, because the app now uses a different method of storing the authorization tokens. Sorry for the inconvenience.*

### Added

- Added status indicator when sending a post (#7)
- Added alert when sending/editing post/comment fails
- Refreh button in the inbox
- Replies sort method for inbox
- Profile view sort method
- Added status indicator when sending a reply
- Multi-account support (#22)
- Confirm delete post/comment
- Blocking users
- Blocking communities
- Update checking on launch
- Image uploading
- Search within community
- OP indicator in the comments
- Post reporting
- Comment reporting
- Bots are now marked with "🤖" emoji
- Support for "!community@instance" and "@user@instance" in Search

### Changes

- **Massive** refactoring of code and general optimization, thanks to [boscojwho](https://github.com/boscojwho) on GitHub
- Post views are now a bit prettier
- Replaced stock AsyncImage with Nuke
- Improved load time of the app (both from cold start and from background)
- API request handler is now running in separate thread, which should speed up the app
- 2FA key field is now always present in the login view (seems like some Lemmy instances change the response text when 2FA is enabled, so the app can't reliably detect if 2FA is enabled)
- Many UI improvements

### Bug Fixes

- Fixed notification counter not updating (#9)
- Unread message count should update, as soon as you reply to a message from the inbox
- Post creation popup content never gets cleared (#6)
- Fixed images in comments overflowing the comment box, if the image was placed in line with text
- Fixed duplicate posts and comments

### Removed

- Experimental settings

## 0.1 (2023-07-17)

- Initial release
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Any code you write must be licensed under GPL-3.0. See [LICENSE](LICENSE.md) for

You can work on whatever you want from [Projects/Leomard](https://github.com/users/Athlon007/projects/3/views/1). If something is in the "In progress" column, please do not work on it, unless you have been assigned to it. First table is for the current version, those features are the most important. Please avoid working on features that are in the "Future" column or the next update column, unless you have been assigned to it. You can also work on stuff from the "Bugs" column.

You can also test stuff that is in the "Testing" column. If you find any bugs related to the stuff in the "Testing" column, please open an issue. If no issues have been reported regarding an item in this column in 7 days after the update containing that item has been released, the item will be moved to the "Done" column.

If you want to work on something that is not in the project, please create an issue first. Your idea will be discussed, and if it is accepted, it will be added to the project.

## Branches
Expand Down
163 changes: 159 additions & 4 deletions Leomard.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"revision" : "c3864b8882bc69f5edfe5c70e18786c91d228b28",
"version" : "12.1.3"
}
},
{
"identity" : "swift-markdown-ui",
"kind" : "remoteSourceControl",
Expand Down

Large diffs are not rendered by default.

110 changes: 77 additions & 33 deletions Leomard/AppStorage/SessionStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,85 @@
import Foundation
import Security
import LocalAuthentication
import SwiftUI

final class SessionStorage {
private static var sessionKey = "key"
private static let defaultLemmy = "lemmy.world"
private static let root = "LeomardApp"

private var currentSession: SessionInfo? = nil
private static let defaultLemmy: String = "lemmy.world" // TODO: Maybe that should be changeable when in guest-mode?
private static let key: String = "LeomardApp"

private var sessions: Sessions

private static let instance = SessionStorage()
public static let getInstance = instance

private init() {
self.currentSession = self.load()
self.sessions = Sessions()
self.sessions = load()
}

public func save(response: SessionInfo) -> Bool {
let context = LAContext()
context.interactionNotAllowed = true

/// Updates the Keychain entry of Leomard.
private func updateKeychain() -> Bool {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let data = try? encoder.encode(response) else {
guard let data = try? encoder.encode(sessions) else {
return false
}

// Remove the existing one first.
_ = self.deleteAll()

let keychainItemQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: SessionStorage.root,
kSecAttrAccount as String: SessionStorage.key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecUseAuthenticationContext as String: context
kSecUseAuthenticationContext as String: self.contextBuilder()
]

let status = SecItemAdd(keychainItemQuery as CFDictionary, nil)

self.currentSession = response
self.sessions = self.load()

return status == errSecSuccess
}

public func load() -> SessionInfo? {
if self.currentSession != nil {
return self.currentSession
/// Saves the current sesssion into sessions storage.
public func setCurrentSession(_ session: SessionInfo) -> Bool {
sessions.currentSession = session

let sessionStored = sessions.allSessions.contains { storedSession in
session == storedSession
}

let context = LAContext()
context.interactionNotAllowed = true
if !sessionStored {
sessions.allSessions.append(session)
}

return self.updateKeychain()
}

/// Returns the current session
public func getCurrentSession() -> SessionInfo? {
return self.sessions.currentSession
}

public func getAllSessions() -> [SessionInfo] {
return self.sessions.allSessions
}

/// Ends the current session.
public func endSession() -> Bool {
sessions.currentSession = nil
return updateKeychain()
}

public func load() -> Sessions {
let keychainItemQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: SessionStorage.root,
kSecAttrAccount as String: SessionStorage.key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationContext as String: context
kSecUseAuthenticationContext as String: self.contextBuilder()
]

var retrievedData: AnyObject?
Expand All @@ -71,33 +96,52 @@ final class SessionStorage {
if let data = retrievedData as? Data {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try? decoder.decode(SessionInfo.self, from: data)
let response = try? decoder.decode(Sessions.self, from: data)
return response ?? Sessions()
}
}

return nil
return Sessions()
}

public func isSessionActive() -> Bool {
return self.currentSession != nil
/// Removes the session from all sessions.
public func remove(session: SessionInfo) -> Bool {
if sessions.allSessions.count == 0 {
return true
}

if session == sessions.currentSession {
sessions.currentSession = nil
}

sessions.allSessions = sessions.allSessions.filter { $0 != session }

return updateKeychain()
}

public func destroy() -> Bool {
/// Removes all stored sessions.
public func deleteAll() -> Bool {
let keychainItemQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: SessionStorage.root
kSecAttrAccount as String: SessionStorage.key
]

let status = SecItemDelete(keychainItemQuery as CFDictionary)
self.currentSession = nil
self.sessions = Sessions()
return status == errSecSuccess
}

public func isSessionActive() -> Bool {
return self.sessions.currentSession != nil
}

public func getLemmyInstance() -> String {
if self.currentSession == nil {
return SessionStorage.defaultLemmy
}

return self.currentSession!.lemmyInstance
return self.getCurrentSession()?.lemmyInstance ?? SessionStorage.defaultLemmy
}

private func contextBuilder() -> LAContext {
let context = LAContext()
context.interactionNotAllowed = true
return context
}
}
6 changes: 5 additions & 1 deletion Leomard/AppStorage/UserPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ final class UserPreferences: ObservableObject {
@AppStorage("comment_sort_method", store: .standard) var commentSortMethod: CommentSortType = .top
@AppStorage("check_notifs_every", store: .standard) var checkNotifsEverySeconds: Int = 60
@AppStorage("unreadonly_when_opening_inbox", store: .standard) var unreadonlyWhenOpeningInbox: Bool = true
@AppStorage("profile_sort_method", store: .standard) var profileSortMethod: SortType = .new
@AppStorage("check_for_update_frequency", store: .standard) var checkForUpdateFrequency: UpdateFrequency = .onceADay

@AppStorage("experiment_x_instance_search", store: .standard) var experimentXInstanceSearch: Bool = false
@AppStorage("skipped_update_version", store: .standard) var skippedUpdateVersion: String = ""
@AppStorage("last_update_check_date", store: .standard) var lastUpdateCheckDate: Date = Date()

let sortTypes: [SortType] = [ .topHour, .topDay, .topMonth, .topYear, .hot, .active, .new, .mostComments ]
let profileSortTypes: [SortType] = [ .topWeek, .topMonth, .topYear, .hot, .active, .new, .mostComments, .old ]
}
36 changes: 34 additions & 2 deletions Leomard/Credits.rtf
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ Copyright (C) 2023 Konrad Figura\

\f2\b Leomard App Icon
\f0\b0 \
vintprox ({\field{\*\fldinst{HYPERLINK "https://github.com/vintprox"}}{\fldrslt https://github.com/vintprox}}) licensed under CC-BY-SA
\f2\b \
vintprox ({\field{\*\fldinst{HYPERLINK "https://github.com/vintprox"}}{\fldrslt https://github.com/vintprox}}) licensed under CC-BY-SA\
\

\f2\b Special thanks to all contributors
\f0\b0 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
\cf0 boscojwho
\f2\b \
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0
\cf0 \
swift-markdown-ui\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
Expand All @@ -56,4 +63,29 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
SOFTWARE.\
\

\f2\b Nuke\
\f0\b0 The MIT License (MIT)\
\
Copyright (c) 2015-2023 Alexander Grebenyuk\
\
Permission is hereby granted, free of charge, to any person obtaining a copy\
of this software and associated documentation files (the "Software"), to deal\
in the Software without restriction, including without limitation the rights\
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
copies of the Software, and to permit persons to whom the Software is\
furnished to do so, subject to the following conditions:\
\
The above copyright notice and this permission notice shall be included in all\
copies or substantial portions of the Software.\
\
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
SOFTWARE.}
Loading

0 comments on commit 3960929

Please sign in to comment.