First and foremost, thank you for taking the time to read this document. We are a community of developers and anime lovers, and we need people like you to help in the development of this project.
If you haven't joined our Discord server already, feel free to come and find us there. You'll get faster responses from our community members and contributors.
In this document, you'll find a set of guidelines for contributing and some resources for getting familiar with NineAnimator's code.
- I just have a question
- How can I contribute?
- What should I know before I get started?
- Styleguides
- Contributing Assets
For faster responses, use our Discord server for questions.
- Chances are your question has been answered by one of our moderators. Make sure to check the
#faq
channel and the pinned messages in the#general
and#help
channels. - If you can't find what you're looking for, post your inquiry in the
#help
channel.
Optionally, you can also use our r/NineAnimator subreddit.
- Report Bugs: Use the issue tracker with the
Bug Report
template to report a bug. - Suggesting Enhancements: Use the issue tracker with the
Feature Request
template to suggest an enhancement. - Help Translating the App: Use our Crowdin site at https://translate.9ani.app to help translate NineAnimator into different languages.
- Code Contribution: Whether you implemented a new anime source or fixed a bug, feel free to open a pull request from your fork. Make sure you read the styleguides section.
- Assets & Designs: Creating a new app icon for NineAnimator? Have suggestions on the designs? See the Contributing Assets section.
Feel free to talk to us on our Discord server before contributing.
NineAnimator is a typical Cocoa Touch iOS application following the Model-View-Controller (MVC) design pattern. There are many resources online for you to learn the MVC design, but in short, you should know the responsibility of each component and keep the additional code at where it should be.
Model: NineAnimator, at its core, is a collection of parsers and analyzers. The NineAnimator/Models
directory hosts all of the parsing logic and user-configurable.
- Anime Source: Under the
Models
folder, you'll find theAnime Source
. Code under this folder fetches data from different source anime websites, decodes it, and present the information to other components of NineAnimator. For each source website, NineAnimator creates a distinctSource
class.Source
encapsulates the functionalities and capabilities of the anime website. - Media Parser: Media Parsers, located under the
Media/Parser
folder in the models, are classes that accept a URL to a streaming site and return a locally streamable URL. Media Parsers are used to support playbacks with native players (and cast). NineAnimator parsers will conform to theVideoProviderParser
protocol. - Anime Listing Service: The list services are third-party tracking and information services implemented under the
Anime List Service
folder under models. List services conform to theListingService
protocol and declare their capabilities through thevar isCapableOf<capability>: Bool
getters. List services also provide the matchingListingAnimeReference
for eachAnimeLink
.
View: The views define the look and feel of the UI components. NineAnimator employs several mechanisms to construct and configure the UI. In general, NineAnimator's design follows that of the latest iOS system apps.
- Storyboards and Xibs: NineAnimator defines most of the UIs with storyboards. We also use auto-layout extensively for adaptive layouts and device variants.
- Theme: Although iOS 13 introduced the system-wide dark mode, to enable backward compatibility with older systems, we still employ our own theming system for light and dark appearances. Each UI component is manually added by
Theme.provision()
(or, for subclasses ofUIView
,.makeThemable()
). Subclasses ofUIView
can either implicitly or explicitly support the theming system. By default, the theming system will configure the views according to types. By conforming to theThemable
protocol, you're explicitly stating support for the theme system and waiving the default behaviors.
Controllers: Controllers of NineAnimator manages the internal flow and logics.
- View Controllers: View controllers instantiate and manage views. In most cases, there will be a convenient method for instantiating view controllers. Optionally, view controllers are also linked by storyboard references. The following is a list of common view controllers in NineAnimator.
- AnimeViewController: The
AnimeViewController
class fetches and presents the correspondAnime
object of anAnimeLink
. Create theAnimeViewController
using storyboard, then use thesetPresenting()
method to configure. - NativePlayerController: The
NativePlayerController
class manages local playbacks of the retrievedPlaybackMedia
instances. You don't instantiateNativePlayerController
directly. Instead, you use theNativePlayerController.default
singleton to retrieve the shared instance and call theplay()
method to start playback. - CastController: The
CastController
manages external playbacks such as Google Cast. UseCastController.default
to retrieve the singleton. Use thepresentPlaybackController()
to present the casting interface. Use thevar isReady: Bool
getter to check if a device has been selected and is ready for playback.
- AnimeViewController: The
- UserNotificationManager: The
UserNotificationManager
manages and update anime subscriptions. Use theUserNotificationManager.default
singleton to retrieve the shared manager. - OfflineContentManager: The
OfflineContentManager
hosts NineAnimator's download system. Use theOfflineContentManager.shared
singleton to access the shared manager.
Most operations in NineAnimator are performed asynchronously (optionally on a different thread). This ensures that any time consuming or intensive tasks won't block the main thread.
At the center of NineAnimator's asynchronous framework is the NineAnimatorPromise
class. This class borrows the idea of promise and bridges the legacy callback mechanisms.
Note: As a safety measure, be sure to maintain a reference to the promise instance for the duration of the task. Losing reference to an unresolved promise will result in the executing task being cancelled. Inside the promise, all references to the blocks or tasks will be removed as soon as the promise task returns.
let promise = NineAnimatorPromise.firstly {
() -> Int in
var result: Int
// Pefrom some operations...
return result
} .then {
previousResult -> Int in
var result: Int
// Some additional operations...
return result
} .thenPromise {
previousResult -> NineAnimatorPromise<Int> in
// Return a promise
.firstly {
var result: Int
// Perform some more operations...
return result
}
} .defer {
thisPromise in
// This block is executed whenever the promises to this point finish
// executing, regardless of success or failiure.
} .error {
error in
// Called when the promise is rejected with an error
} .finally {
finalResult in
// Called when the promise is resolved (successfully)
// All previous promises are not executed until the `finally` block
// is added.
}
In general, use descriptive languages for commit messages. Explain what you add, changed, or deleted ("Fix a problem that causes the app to crash in the Library scene" not "fix a problem"). Reference any issue if the commit is related to one.
Before committing, make sure the compiler doesn't complain and swiftlint
doesn't give out warnings.
Avoid trivial ("Oops!") commits. Whenever possible, amend your existing commits before pushing or submitting a pull request.
Do as much as related in a single commit. For example, if you're renaming a list of files, don't commit for each rename operation. Instead, commit once for all name changes.
A few points to keep in mind:
- Use implicit returns for single-line functions and closures.
- Avoid implicit unwrapping properties and variables (
var data: Data! { get }
). - Prefer shorter class names (
LibrarySceneController
notLibraryTabSceneCollectionViewController
). - Prefer extensions over single files. Split large files into multiple smaller files with
+
extensions (ex.User.swift
,User+Preferences.swift
,User+History.swift
).
We use swiftlint
to ensure the tidiness of our code. Before submitting your code, run swiftlint
to check for potential styling violations.
Please use our Discord server for suggestions related to UI designs and visual experiences.
- App Icon Submissions: please submit your design to the #app-icon-suggestions channel. Community-contributed app icons should be rendered with a resolution of 180x180 or higher.
- Visuals & Designs: please send your design or suggestions to the #suggestions channel.