diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a1803bcc3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,379 @@ +# CONTRIBUTING.md aka «the trash Panda's guide to the galaxy» + +## 0. Introduction + +### 0.1 Welcome + +First of all, a word of welcoming to whomever has landed on this page. If you are here, that means +that somehow you have found a link to this project over the Internet, you are possibly interested in +it (either positively or negatively) and you are wondering whether you can contribute in any way. + +This is great news, so thank you for your time and dedication. Whether you spend 1 minute or 1 hour +here, it is really highly appreciated. + +### 0.2 ToC + +With no further ado, here is a short list of what you'll find here. Keep in mind that, like the +project in under continuous development and evolution (hopefully), so is this guide. New sections +will be added and new answers will be provided as long as questions are submitted. + +- [section 1](#1-project-overview) will provide a general overview about the project and its + contributors: who we are, what we are trying to achieve, why are we doing it and how. +- [section 2](#2-community) illustrates the core values that are behind this project, its key goals + and an acknowledgement to all the people who helped us to achieve those targets. +- [section 3](#3-how-tos) is a collection of operative instructions for those who want to + contribute, either reporting bugs, asking for new features, submitting feedback, contributing with + code, + documentation or translations +- [section 4](#4-setup-the-development-environment) is dedicated to developers wanting to build the + project locally in order to submit pull requests +- [section 5](#5-project-structure) contains a technical illustration of the tech stack used in the + project, the architectural design used in the various features and how the project is broken down + into discrete components that interact with each other +- [section 6](#6-coding-conventions) contains the coding conventions that you should follow when + submitting PRs, because readability and consistency matter and there are some rules (with + exceptions) that should ideally be followed everywhere. + +## 1 Project overview + +### 1.1 Purpose + +Raccoon for Lemmy was designed with three goals in mind: on the one side experiment and put to some +stress test with a real world project Kotlin Multiplatform (KMP) and Compose multiplatform, in order +to explore what could be achieved with multiplatform libraries and share as much code as possible +in the `commonMain` source directory. The Android ecosystem of Lemmy apps has many cross-platform +alternatives implemented with Flutter or React, a couple of native options written in Java and just +one native pure-Kotlin application. Raccoon intends to be an Android-first app, but +experimentation on other OSes given the multiplatform nature of its technologies are not +excluded _a priori_. + +The second goal was to offer a feature rich Lemmy client mainly aimed at "pro" users, i.e. users who +are not content with just browsing the contents of the Fediverse, creating posts and +answers. This is why Raccoon tried to offer from the very beginning features like: + +- a full-fledged explore section that allows multiple result types +- instance info, with the list of communities of foreign instances as well as the possibility to + visit communities on foreign instances in guest mode +- multi-community, i.e. community aggregation +- moderation tools (for community moderators) and access to community moderation log. + +Finally, the third goal was to offer a Lemmy experience where everyone could "feel at home" (see the +[Code of Conduct](#22-code-of-conduct) below). This implies keeping the interface lean by default +but having the possibility to customize a variety of aspects (theme, vote format, default feed/sort +type) and adopt the user's native language. As a consequence, leanness, personalization and +localization are first-class citizens in this project. + +### 1.2 About the name + +Lemmy users used to refer to themselves as "lemmings" and the platform logo clearly recalls that +animal, so there was this tradition in the early days to use animals names for clients or at least +the original developers of this app thought so (some examples being Jerboa and Fennec). And they +really liked raccoons so they choose that. Raccoons are cunning animals that adapt to live in harsh +conditions, finding a way to survive even with what we consider "trash". They look like masked +criminals but do not harm anyone, they are lazy and chubby and so cute in the end. + +In this group, we often quote every raccoon's motto: «Live Fast, Eat Trash» (abbreviated LFET). + +## 2 Community + +### 2.1 About us + +This section is probably going to change over time, anyway we are a small group of volunteers who in +their spare time contribute to the project. + +You can find us on [this](https://matrix.to/#/#raccoonforlemmy:matrix.org) Matrix space, where you +can ask more questions in the General room or by writing directly to the administrator. + +### 2.2 Code of Conduct + +When interacting with other members of the community, be them end-users, developers, supporters or +whatever, remember that we are raccoons, and we follow these principles: + +| | Principle | Explanation | +|:-:|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| R | **Respect** | We respect each other as people, remembering we are part of a group that goes beyond individual opinions, beliefs, preferences and habits. | +| A | **Availability** | We support each other with the skills and available time that we have, considering that we are volunteers and we operate on a best-effort basis. | +| C | **Commitment** | We are responsible for our choices and we agree that our choices are taken wisely, always considering the impact on other members of the group. | +| C | **Cohesion** | We remember that we are part of a community which is bigger than the individual members, so we do unto others as we would have them do unto us. | +| O | **Objectivity** | We are committed to telling the truth in the most objective way and, if we express subjective opinions, to do it in a clear and constructive way. | +| O | **Originality** | We bring our own personal experience and ideas which are acceptable even if "different": everyone's voice matters and deserves to be listened. | +| N | **Never give up** | We are tenacious and are not afraid by technical challenges, we embrace every difficult task as an opportunity to learn and acquire new skills. | + +### 2.3 Acknowledgements + +This project would not be what it is were it not for the huge amount of patience and dedication of +early adopters who sent me continuous feedback and ideas for improvement after every release, +reported bugs, offered to help, submitted translations to their local language, etc. + +A special thank to all those who contributed so far (in nearly chronological order): + +- [u/rb_c](https://discuss.tchncs.de/u/rb_c) +- [u/heyazorin](https://lemmy.ml/u/heyazorin) +- [u/thegiddystitcher](https://lemm.ee/u/thegiddystitcher) +- [u/SgtAStrawberry](https://lemmy.world/u/SgtAStrawberry) +- [outerair](https://github.com/outerair) +- [u/Wild_Mastic](https://lemmy.world/u/Wild_Mastic) +- all those who reported feedback and ideas through the Lemmy community, GitHub issues, emails, + private messages, homing pidgeons and every other imaginable media. + +## 3. HOW-TOs + +### 3.1 General interactions + +There are many ways you can interact with members of the community: + +- use the [Matrix space](https://matrix.to/#/#raccoonforlemmy:matrix.org) to chat in real time with + other team members, there are two rooms in the space: "General" is for general information about + the app development and "Trashcan" is more for smalltalk and random topics. +- use the [Lemmy community](https://lemmy.world/c/raccoonforlemmy) to receive updates about the new + releases, participate into public discussions in the Lemmy style and provide your feedback or even + share your memes about raccoons with any other interested people +- use the [GitHub issue tracker](https://github.com/diegoberaldin/RaccoonForLemmy/issues) to report + bugs or request features +- use the [GitHub discussion section](https://github.com/diegoberaldin/RaccoonForLemmy/discussions) + for technical questions about the release roadmap, questions about app internationalization, etc. +- finally, if none of the above methods fits your needs you can write an email or send a private + message to the original developer (my personal information are in the GitHub profile). + +Always remember the guidelines contained in the [Code of Conduct](#22-code-of-conduct) to interact +with other people: our values are important and should be applied even with each other and not only +with end users. + +### 3.2 Create bug reports + +The preferred way to submit a bug report is +the [GitHub issue tracker](https://github.com/diegoberaldin/RaccoonForLemmy/issues). + +- use the "New issue" button to open the creation form +- in the "Add a title" field give a synthetic description of the issue, if it affects a specific + part of the application you can add in square brackets a prefix containing that information, + e.g. `[post list]` or `post detail` or `[create report]`. +- in the "Add a description" field provide a more detailed explanation of the issue, you can use + Markdown syntax (in the GitHub flavour) to format text here. + An ideal bug report contains: + - the precondition (sequence of steps that lead to the faulty condition, if it is + deterministic) + - a description of the error condition (with the expected result and the actual + result, where the difference between the two is clear) + - (if you can, optionally) some screenshots that make it clear where the error is +- you can add the "bug" label to the issue or otherwise leave it blank. Do not use the other + fields (assignee, milestone, etc.) because they are used internally by the team members who will + take the issue in charge during resolution. + +### 3.3 Request features + +As for bug reports, the preferred way for feature or change requests is +the [GitHub issue tracker](https://github.com/diegoberaldin/RaccoonForLemmy/issues). +In this case, you should: + +- insert a short description in the title field, if the feature is specific to some app domain you + can use the same square bracket prefix as in bug reports +- insert a more detailed explanation of your idea in the description field, if your feature is + present in other apps you can provide screenshots or make references to make the request clearer +- you can use the "feature" label to tag the issue, or otherwise leave it blank and let the + maintainers decide how to triage the issue. + +### 3.4 Add a new localization + +The preferred way for localizations (l10ns) is to submit a pull request (PR) as detailed in +the [next section](#35-submit-a-pull-request). The project uses +the [moko-resources](https://github.com/icerockdev/moko-resources) library for multiplatform access +to resource files, which for l10n implies having to deal with XMLs in the Android style. + +You will have to create a new folder under the `resources/src/commonMain/resources/MR` directory +named after the locale you want to add (following IANA conventions for locales) and create +a `strings.xml` file in it, copying the contents +of [this file](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/resources/src/commonMain/resources/MR/base/strings.xml) +except for the items that are marked as `translatable="false"` (you can safely remove them). + +Modify +the [base](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/resources/src/commonMain/resources/MR/base/strings.xml) +l10n to add the name of your language in the untranslatable set + +```xml + +YYY +``` + +where XXX is the IANA locale code and YYY if the name you want to appear in the UI (use the endonym +of your language, i.e. the name of that language in the language itself). + +After which you'll have to make sure the project compiles, because there are some syntax rules that +must be enforced in string files, e.g.: + +- apostrophes (`'`) must be escaped with a backslash (`\'`) +- some special characters must be represented as XML entities so `>` becomes `>`, `<` + becomes `<`, `&` becomes `&s;` and so on… (talking of ellipsis, use `…` preferably instead + of three dots) + +If you want you can change the code in the following spots: + +- add your flag and language name (mapping your language code to the values) in + [Extensions.kt](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/core/utils/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/utils/Extensions.kt) +- add an option (using your language code) in the `values` array + in [LanguageBottomSheet.kt](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/core/commonui/modals/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/modals/LanguageBottomSheet.kt) + +That's it. You can test that everything works by launching the development app. + +However, if you are not a developer and do not feel confident with GitHub's PR mechanism, you can +just download +the [base l10n](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/resources/src/commonMain/resources/MR/base/strings.xml) +to your local machine, edit the file and send an email to the maintainers with the attachment, we +will take care of the rest. + +If you have proposals, want to submit l10n fixes/improvements to existing ones, you can +use [this discussion](https://github.com/diegoberaldin/RaccoonForLemmy/discussions/378) and post an +answer, we will reach out to you and solve the issue. + +### 3.5 Submit a pull request + +First of all, please fork the repository by using the "Create a new fork" button in the GitHub +console, which will create a copy of the repository in your personal account. + +Create a branch from the repository's master branch named after the feature/change you want to +submit, use whatever convention you like (preferably underscores to separate words but as you wish) +and make any commits you want on it. + +Push the commits on the remote branch of your forked repository. + +Use the "Compare and pull request" button to see a recap of the PR, after which use the "Create pull +request" to submit the request to the upstream repository. + +## 4. Setup the development environment + +This is a Kotlin Multiplatform (KMP) project that uses the Gradle build tool. The recommended +development environment is Android Studio with the Kotlin Multiplatform Mobile plugin installed. +S +ince the project is using Gradle 8.2.1 with the Android Gradle Plugin (AGP) version 8.2.1 you +should use Android Studio Hedgehog or later (have a +look [here](https://developer.android.com/build/releases/gradle-plugin?hl=en#android_gradle_plugin_and_android_studio_compatibility) +for a compatibility matrix between versions of Gradle, AGP and Android Studio). +Alternatively, you can try and use IntelliJ IDEA or Fleet but some extra steps may be needed to +ensure everything fits and runs together. + +In order for Gradle to build, you will need to have a JDK installed on your local development +machine, if you are using stock Android Studio it ships with the JetBrains runtime, you could have a +look in the Settings dialog under the section "Build, Execution, Deployment > Build Tools > Gradle" +in the "Gradle JDK" location drop-down menu. If you want to use your custom JDK (e.g. under Linux +you want to try OpenJDK instead), please make sure that it has a suitable version, according +to [this page](https://docs.gradle.org/8.2/userguide/compatibility.html), so between 8 and 21. + +Finally, since building this project requires a lot of RAM due to its multi-module structure and to +the fact that it is quite a complex project, please make sure that the `gradle.properties` file in +the root folder contains proper memory settings for the JVM and the Kotlin compile daemon: + +```properties +org.gradle.jvmargs=-Xmx8192M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx8192M" +``` + +## 5. Project structure + +This is a Gradle project, it is setup to download a Gradle distribution and resolve dependencies +according to the definitions contained in the `gradle/libs.versions.toml` file. + +Also, please note that in the `settings.gradle.kts` file we are using the option: + +``` +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +``` + +to reference Gradle subprojects in each `build.gradle.kts` with a type safe notation +e.g. `implementation(projects.core.utils)`. + +### 5.1 Tech stack + +Here is a list of the technologies used in the project, with a short historical explanation in the +cases where the initial choice changed over time and the reasons why the change was made. + +
+
Dependency Injection
+
+The choice here is the Koin library. The main +reason it was selected because of its great multiplatform support and the integration with the +navigation library (which at the beginning of the project was not there yet, but was added later and +proved to work great). You can find module definitions (beware, Gradle modules and Koin modules are +two different concepts and should not be confused) in a `di` package inside each subproject, modules +can include each other and all top-level modules are included in the shared module, more on it in +"Module overview and dependencies". +
+
Navigation
+
+For navigation the Voyager library has been +selected. Again, the choice was driven by its multi-platform support, its very practical approach +and ease to set up and get going, compared to other multi-platform libraries like Decompose that +were used in the past. Nonetheless, and this lesson was learned the hard way, easiness of use and +compactness mean that things will go smooth in the future, and as the project grew the navigation +library started to show its limits. Part of them were addressed by encapsulating the transition +logic (to push/pop screens into the navigation stack and open/close modal bottom sheets) into a +centralized component NavigationCoordinator.kt. +Something similar was done for the navigation drawer in DrawerCoordinator.kt. +Even the DI integration with Koin was not pain-free, the `:core:navigation` module contains some +glue code that is used to work around some of the issues that were encountered. +
+
Networking
+
Here, at least for Android developers, no surprises: the choice was Ktor +which has great multiplatform support. Instead of using Retrofit, to create network adapters the +Ktorfit library is used, which uses KSP to parse +annotations and generate code. +
+
Resource management
+
+The Moko resources library is used as part +of the Moko project. This was a choice I never regretted, it works great (even in edge cases such as +dynamic language configuration independent of device settings, which was a project must-have from +the beginning) and makes it really easy to access strings, icons and fonts in a multiplatform environment. +
+
Image loading
+
+This was something that was expected to be simpler but unfortunately it wasn't. Popular kotlin libraries +do not support multiplatform yet and the only library that advertised it was Kamel +which had a major bug while rendering large images, and the community proved quite unfriendly/unwelcoming +when reporting it. The project was already relying on Kamel for many things, from loading images on demand to +Markdown rendering, so deciding to switch was not easy at all. In the end, the iOS part of the project +continues using it (Raccoon for iOS has by far bigger problems than image rendering, being a virtually +inexistent platform) while the Android counterpart was migrated to Coil. +Things will change when Coil 3.x will be release, because it will go multiplatform as well. +
+
Preference storage
+
+Here the choice was the Multiplatform settings +libary which not only works great but also offers support for encryption. +
+
Primary persistence
+
+This project was a chance to experiment with SQLDelight +(in other multiplatform projects other libraries were tested like Exposed), whereas database encryption +is obtained through [SQLCipher Android](https://www.zetetic.net/sqlcipher/sqlcipher-for-android/), formerly +Android Database SQLCipher. +
+
Markdown rendering
+
+This was another part, like image loading, where KMP is still lacking and things are far more +complicated than it should be. The first approach that was used in the project, and which still survives +in the iOS platform (being it "no man's land" currently) involved using JetBrain's Markdown +library for parsing in conjunction with custom Compose rendering inspired by +Multiplatform Markdown Renderer. +This approach was promising in the beginning but it has proven to grow more and more difficult to +support custom Markdown features, such as Lemmy spoilers. For this reason, the Android counterpart +has been completely refactored and migrated to the Markwon +library which is more flexible/extensible albeit more complicated to use, especially if called from +a multiplatform environment with `expect`/`actual` functions (and image opening/URL opening/custom links +like Lemmy URL references have to be managed). The big star here is MarkwonProvider and its implementation +DefaultMarkwonProvider.kt. +Parts of the Markwon configuration and usage is inspired by Jerboa for Lemmy. +
+
+ +### 5.2 Module overview and dependencies + +### 5.3 Architectural patterns + +## 6. Coding conventions + +### 6.1 Modularization strategy + +### 6.2 General Kotlin rules + +### 6.3 Compose rules + +### 6.4 Test structure diff --git a/README.md b/README.md index c33da3cde..45d88ffec 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ multiplatform and gradually grew as a fully functional client with many features ## Main features - view post feed and comments with different listing and sort types; -- possibility to upvote and downvote (with optional swipe actions) +- possibility to upvote and downvote (with optional swipe actions); - community and user detail; - user profile with one's own posts, comments and saved items; - inbox with replies, mentions and direct messages; @@ -122,11 +122,11 @@ Moreover, developing a new client was an opportunity to add all the features tha were "scattered" across different apps, e.g. I liked the feature richness of [Liftoff](https://github.com/liftoff-app/liftoff) (e.g. the possibility to explore all the communities of an external instance in guest mode), the multi-community feature of -[Summit](https://github.com/idunnololz/summit-for-lemmy) and the polished UI of +[Summit](https://github.com/idunnololz/summit-for-lemmy) and the polished UI of the really great [Thunder](https://github.com/thunder-app/thunder) and I wished I could have them all in the same app. -This involves a high level of discretionality and personal taste, I know, but this project _is_ all +This involves a high level of discretion and personal taste, I know, but this project _is_ all about experimenting and learning. ## Technologies used: @@ -134,40 +134,15 @@ about experimenting and learning. - [Koin](https://github.com/InsertKoinIO/koin) for dependency injection - [Voyager](https://github.com/adrielcafe/voyager) for screen navigation - [Ktor](https://github.com/ktorio/ktor) and [Ktorfit](https://github.com/Foso/Ktorfit) for - networking in conjunction with kotlinx-serialization for JSON marshalling + networking - [Moko resources](https://github.com/icerockdev/moko-resources) for resource management -- [Kamel](https://github.com/Kamel-Media/Kamel) for lazy image loading, but later switched to - [Coil](https://github.com/coil-kt/coil) on Android because there was a major bug with large images - [Multiplatform settings](https://github.com/russhwolf/multiplatform-settings) for encrypted preferences - [SQLDelight](https://github.com/cashapp/sqldelight) and [SQLCipher](https://github.com/sqlcipher/sqlcipher) for local persistence -- for Markdown rendering, the initial approach involved using - [Markdown](https://github.com/JetBrains/markdown) for parsing in conjunction with custom rendering - but this approach proved difficult to extend and maintain so on Android the - [Markwon](https://github.com/noties/Markwon) library was chosen instead. -## Credits: - -- the `core-md` module in the iOS flavor is inspired by - [Multiplatform Markdown Renderer](https://github.com/mikepenz/multiplatform-markdown-renderer) and - the Android implementation with Markwon is adapted from - [Jerboa for Lemmy](https://github.com/dessalines/jerboa) -- the UI is inspired by the really great [Thunder](https://github.com/thunder-app/thunder) app - -## Acknowledgements: - -This project would not be what it is were it not for the huge amount of patience and dedication -of early adopters who sent me continous feedback and ideas for improvement after every release. - -A special thank to all those who contributed so far: - -- [u/rb_c@discuss.tchncs.de](https://discuss.tchncs.de/u/rb_c) -- [u/heyazorin@lemmy.ml](https://lemmy.ml/u/heyazorin) -- [u/thegiddystitcher@lemm.ee](https://lemm.ee/u/thegiddystitcher) -- [outerair](https://github.com/outerair) -- all those who reported feedback and ideas through the Lemmy community, GitHub issues, emails, - private messages, homing pidgeons and every other imaginable media. +More info about the technologies used in the project can be found in +the [CONTRIBUTING.md](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/CONTRIBUTING.md#51-tech-stack). ## Want to try it out?