Add some missing information to CONTRIBUTING.md guide (#11312)
* [skip ci] Add some missing information to CONTRIBUTING.md guide. * Add information about the chapter parsing. Also add an UriPartFilter example. * [skip ci] Fix some typos.
This commit is contained in:
parent
05330d0717
commit
6b3a52ddd7
|
@ -31,6 +31,8 @@ The quickest way to get started is to copy an existing extension's folder struct
|
||||||
|
|
||||||
Each extension should reside in `src/<lang>/<mysourcename>`. Use `all` as `<lang>` if your target source supports multiple languages or if it could support multiple sources.
|
Each extension should reside in `src/<lang>/<mysourcename>`. Use `all` as `<lang>` if your target source supports multiple languages or if it could support multiple sources.
|
||||||
|
|
||||||
|
The `<lang>` used in the folder inside `src` should be the major `language` part. For example, if you will be creating a `pt-BR` source, use `<lang>` here as `pt` only. Inside the source class, use the full locale string instead.
|
||||||
|
|
||||||
#### Extension file structure
|
#### Extension file structure
|
||||||
|
|
||||||
The simplest extension structure looks like this:
|
The simplest extension structure looks like this:
|
||||||
|
@ -157,10 +159,9 @@ The class which is referenced and defined by `extClass` in `build.gradle`. This
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| `name` | Name displayed in the "Sources" tab in Tachiyomi. |
|
| `name` | Name displayed in the "Sources" tab in Tachiyomi. |
|
||||||
| `baseUrl` | Base URL of the source without any trailing slashes. |
|
| `baseUrl` | Base URL of the source without any trailing slashes. |
|
||||||
| `lang` | An ISO 639-1 compliant language code (two letters in lower case). |
|
| `lang` | An ISO 639-1 compliant language code (two letters in lower case in most cases, but can also include the country/dialect part by using a simple dash character). |
|
||||||
| `id` | Identifier of your source, automatically set in `HttpSource`. It should only be manually overriden if you need to copy an existing autogenerated ID. |
|
| `id` | Identifier of your source, automatically set in `HttpSource`. It should only be manually overriden if you need to copy an existing autogenerated ID. |
|
||||||
|
|
||||||
|
|
||||||
### Extension call flow
|
### Extension call flow
|
||||||
|
|
||||||
#### Popular Manga
|
#### Popular Manga
|
||||||
|
@ -168,9 +169,9 @@ The class which is referenced and defined by `extClass` in `build.gradle`. This
|
||||||
a.k.a. the Browse source entry point in the app (invoked by tapping on the source name).
|
a.k.a. the Browse source entry point in the app (invoked by tapping on the source name).
|
||||||
|
|
||||||
- The app calls `fetchPopularManga` which should return a `MangasPage` containing the first batch of found `SManga` entries.
|
- The app calls `fetchPopularManga` which should return a `MangasPage` containing the first batch of found `SManga` entries.
|
||||||
- This method supports pagination. When user scrolls the manga list and more results must be fetched, the app calls it again with increasing `page` values(starting with `page=1`). This continues until `MangasPage.hasNextPage` is passed as `true` and `MangasPage.mangas` is not empty.
|
- This method supports pagination. When user scrolls the manga list and more results must be fetched, the app calls it again with increasing `page` values (starting with `page=1`). This continues until `MangasPage.hasNextPage` is passed as `true` and `MangasPage.mangas` is not empty.
|
||||||
- To show the list properly, the app needs `url`, `title` and `thumbnail_url`. You must set them here. The rest of the fields could be filled later.(refer to Manga Details below)
|
- To show the list properly, the app needs `url`, `title` and `thumbnail_url`. You **must** set them here. The rest of the fields could be filled later (refer to Manga Details below).
|
||||||
- You should set `thumbnail_url` if is available, if not, `fetchMangaDetails` will be **immediately** called.(this will increase network calls heavily and should be avoided)
|
- You should set `thumbnail_url` if is available, if not, `fetchMangaDetails` will be **immediately** called (this will increase network calls heavily and should be avoided).
|
||||||
|
|
||||||
#### Latest Manga
|
#### Latest Manga
|
||||||
|
|
||||||
|
@ -183,7 +184,33 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late
|
||||||
|
|
||||||
- When the user searches inside the app, `fetchSearchManga` will be called and the rest of the flow is similar to what happens with `fetchPopularManga`.
|
- When the user searches inside the app, `fetchSearchManga` will be called and the rest of the flow is similar to what happens with `fetchPopularManga`.
|
||||||
- If search functionality is not available, return `Observable.just(MangasPage(emptyList(), false))`
|
- If search functionality is not available, return `Observable.just(MangasPage(emptyList(), false))`
|
||||||
- `getFilterList` will be called to get all filters and filter types. **TODO: explain more about `Filter`**
|
- `getFilterList` will be called to get all filters and filter types.
|
||||||
|
|
||||||
|
##### Filters
|
||||||
|
|
||||||
|
The search flow have support to filters that can be added to a `FilterList` inside the `getFilterList` method. When the user changes the filters' state, they will be passed to the `searchRequest`, and they can be iterated to create the request (by getting the `filter.state` value, where the type varies depending on the `Filter` used). You can check the filter types available [here](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt) and in the table below.
|
||||||
|
|
||||||
|
| Filter | State type | Description |
|
||||||
|
| ------ | ---------- | ----------- |
|
||||||
|
| `Filter.Header` | None | A simple header. Useful for separating sections in the list or showing any note or warning to the user. |
|
||||||
|
| `Filter.Separator` | None | A line separator. Useful for visual distinction between sections. |
|
||||||
|
| `Filter.Select<V>` | `Int` | A select control, similar to HTML's `<select>`. Only one item can be selected, and the state is the index of the selected one. |
|
||||||
|
| `Filter.Text` | `String` | A text control, similar to HTML's `<input type="text">`. |
|
||||||
|
| `Filter.CheckBox` | `Boolean` | A checkbox control, similar to HTML's `<input type="checkbox">`. The state is `true` if it's checked. |
|
||||||
|
| `Filter.TriState` | `Int` | A enhanced checkbox control that supports an excluding state. The state can be compared with `STATE_IGNORE`, `STATE_INCLUDE` and `STATE_EXCLUDE` constants of the class. |
|
||||||
|
| `Filter.Group<V>` | `List<V>` | A group of filters (preferentially of the same type). The state will be a `List` with all the states. |
|
||||||
|
| `Filter.Sort` | `Selection` | A control for sorting, with support for the ordering. The state indicates which item index is selected and if the sorting is `ascending`. |
|
||||||
|
|
||||||
|
All control filters can have a default state set. It's usually recommended if the source have filters to make the initial state match the popular manga list, so when the user open the filter sheet, the state is equal and represents the current manga showing.
|
||||||
|
|
||||||
|
The `Filter` classes can also be extended, so you can create new custom filters like the `UriPartFilter`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
||||||
|
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||||
|
fun toUriPart() = vals[state].second
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Manga Details
|
#### Manga Details
|
||||||
|
|
||||||
|
@ -194,27 +221,44 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late
|
||||||
- `SManga.genre` is a string containing list of all genres separated with `", "`.
|
- `SManga.genre` is a string containing list of all genres separated with `", "`.
|
||||||
- `SManga.status` is an "enum" value. Refer to [the values in the `SManga` companion object](https://github.com/tachiyomiorg/extensions-lib/blob/9733fcf8d7708ce1ef24b6c242c47d67ac60b045/library/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt#L24-L27).
|
- `SManga.status` is an "enum" value. Refer to [the values in the `SManga` companion object](https://github.com/tachiyomiorg/extensions-lib/blob/9733fcf8d7708ce1ef24b6c242c47d67ac60b045/library/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt#L24-L27).
|
||||||
- During a backup, only `url` and `title` are stored. To restore the rest of the manga data, the app calls `fetchMangaDetails`, so all fields should be (re)filled in if possible.
|
- During a backup, only `url` and `title` are stored. To restore the rest of the manga data, the app calls `fetchMangaDetails`, so all fields should be (re)filled in if possible.
|
||||||
- If a `SManga` is cached `fetchMangaDetails` will be only called when the user does a manual update(Swipe-to-Refresh).
|
- If a `SManga` is cached, `fetchMangaDetails` will be only called when the user does a manual update (Swipe-to-Refresh).
|
||||||
- `fetchChapterList` is called to display the chapter list.
|
- `fetchChapterList` is called to display the chapter list.
|
||||||
- The list should be sorted descending by the source order.
|
- **The list should be sorted descending by the source order**.
|
||||||
- If `Page.imageUrl`s are available immediately, you should pass them here. Otherwise, you should set `page.url` to a page that contains them and override `imageUrlParse` to fill those `imageUrl`s.
|
|
||||||
|
|
||||||
#### Chapter
|
#### Chapter
|
||||||
|
|
||||||
- After a chapter list for the manga is fetched and the app is going to cache the data, `prepareNewChapter` will be called.
|
- After a chapter list for the manga is fetched and the app is going to cache the data, `prepareNewChapter` will be called.
|
||||||
- `SChapter.date_upload` is the [UNIX Epoch time](https://en.wikipedia.org/wiki/Unix_time) **expressed in miliseconds**.
|
- `SChapter.date_upload` is the [UNIX Epoch time](https://en.wikipedia.org/wiki/Unix_time) **expressed in milliseconds**.
|
||||||
- If you don't pass `SChapter.date_upload`, the user won't get notifications for new chapters. refer to [this issue](https://github.com/tachiyomiorg/tachiyomi/issues/2089) for more info. `System.currentTimeMillis()` works as a substitute when real data is not available.
|
- If you don't pass `SChapter.date_upload`, the app will use the fetch date instead, but it's recommended to always fill it if it's available.
|
||||||
|
- To get the time in milliseconds from a date string, you can use a `SimpleDateFormat` like in the example below.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private fun parseDate(dateStr: String): Long {
|
||||||
|
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
|
||||||
|
.getOrNull() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DATE_FORMATTER by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you make the `SimpleDateFormat` a class constant or variable so it doesn't get recreated for every chapter.
|
||||||
|
- If the parsing have any problem, make sure to return `0L` so the app will use the fetch date instead.
|
||||||
|
|
||||||
#### Chapter Pages
|
#### Chapter Pages
|
||||||
|
|
||||||
- When user opens a chapter, `fetchPageList` will be called and it will return a list of `Page`s.
|
- When user opens a chapter, `fetchPageList` will be called and it will return a list of `Page`s.
|
||||||
- While a chapter is open in the reader or is being downloaded, `fetchImageUrl` will be called to get URLs for each page of the manga.
|
- While a chapter is open in the reader or is being downloaded, `fetchImageUrl` will be called to get URLs for each page of the manga if the `Page.imageUrl` is empty.
|
||||||
|
- If the source provides all the `Page.imageUrl`'s directly, you can fill them and let the `Page.url` empty, so the app will skip the `fetchImageUrl` source and call directly `fetchImage`.
|
||||||
- Chapter pages numbers start from `0`.
|
- Chapter pages numbers start from `0`.
|
||||||
|
|
||||||
### Misc notes
|
### Misc notes
|
||||||
|
|
||||||
- Sometimes you may find no use for some inherited methods. If so just override them and throw exceptions: `throw UnsupportedOperationException("Not used.")`
|
- Sometimes you may find no use for some inherited methods. If so just override them and throw exceptions: `throw UnsupportedOperationException("Not used.")`
|
||||||
- You probably will find `getUrlWithoutDomain` useful when parsing the target source URLs.
|
- You probably will find `getUrlWithoutDomain` useful when parsing the target source URLs. Keep in mind there's a current issue with spaces in the URL though, so if you use it, replace all spaces with URL encoded characters (like `%20`).
|
||||||
- If possible try to stick to the general workflow from `HttpSource`/`ParsedHttpSource`; breaking them may cause you more headache than necessary.
|
- If possible try to stick to the general workflow from `HttpSource`/`ParsedHttpSource`; breaking them may cause you more headache than necessary.
|
||||||
- By implementing `ConfigurableSource` you can add settings to your source, which is backed by [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences).
|
- By implementing `ConfigurableSource` you can add settings to your source, which is backed by [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue