Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup Dokka to generate html docs and document most our external functions and properties #52

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@
.externalNativeBuild
.cxx
local.properties

# Generated documentation
docs/dokka
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ the [jlleitschuh/ktlint-gradle](https://github.com/jlleitschuh/ktlint-gradle) wr
Check the style of the whole project or just the desired module (library or demo app) with the
following commands:

```
```sh
./gradlew ktlintCheck
./gradlew :gravatar:ktlintCheck
./gradlew :app:ktlintCheck
```

You can also try to let Ktlint fix the code style issues. Just use:

```
```sh
./gradlew ktlintFormat
./gradlew :gravatar:ktlintFormat
./gradlew :app:ktlintFormat
Expand All @@ -42,7 +42,7 @@ You can also try to let Ktlint fix the code style issues. Just use:
We use [Detekt](https://github.com/detekt/detekt) to perform static code analysis. You can run
Detekt via a gradle command:

```
```sh
./gradlew detekt
./gradlew :gravatar:detekt
./gradlew :app:detekt
Expand Down Expand Up @@ -71,3 +71,10 @@ dependencies {
}
```

## Generating the API documentation

We're using [kdoc](https://kotlinlang.org/docs/kotlin-doc.html) to document the library's code. [Dokka](https://kotlinlang.org/docs/dokka-introduction.html) has been setup to generate html documentation from kdoc. To generate the HTML docs in `docs/dokka/`, run the following command:

```sh
./gradlew dokkaHtml
```
11 changes: 10 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.dokka.gradle.DokkaTask

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
Expand All @@ -8,4 +10,11 @@ plugins {

// Detekt
id("io.gitlab.arturbosch.detekt") version "1.23.4" apply false
}

// Dokka
id("org.jetbrains.dokka") version "1.9.10"
}

tasks.withType<DokkaTask>().configureEach {
notCompatibleWithConfigurationCache("https://github.com/Kotlin/dokka/issues/2231")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why do we need this in the project build.gradle? We only want to apply it to the SDK module.

Copy link
Member

@mlumeau mlumeau Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to apply it to the SDK module.

And ultimately, every library module if/when we split the SDK in separate components. The only module where we won't need it is the test app.

Copy link
Contributor Author

@maxme maxme Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this in the project build.gradle?

That's a remainder of the configuration cache issues and workaround. I tried to move everything to the sub-module and it seems to work fine, so I'll move it there. We can change it later: when we add more modules or if it's still causing issues.

10 changes: 10 additions & 0 deletions gravatar/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ plugins {
// Detekt
id("io.gitlab.arturbosch.detekt")
id("com.automattic.android.publish-to-s3")

// Dokka
id("org.jetbrains.dokka")
}

android {
Expand Down Expand Up @@ -50,6 +53,11 @@ android {
isIncludeAndroidResources = true
}
}

tasks.dokkaHtml.configure {
outputDirectory.set(file("../docs/dokka"))
notCompatibleWithConfigurationCache("https://github.com/Kotlin/dokka/issues/2231")
}
}

dependencies {
Expand All @@ -65,6 +73,8 @@ dependencies {

androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

dokkaPlugin("org.jetbrains.dokka:android-documentation-plugin:1.9.10")
}

project.afterEvaluate {
Expand Down
49 changes: 48 additions & 1 deletion gravatar/src/main/java/com/gravatar/DefaultAvatarImage.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,77 @@
package com.gravatar

/**
* The default avatar image to use when the user does not have a Gravatar image.
*/
sealed class DefaultAvatarImage {
/**
* @suppress
*/
abstract class Predefined(val style: String) : DefaultAvatarImage()

/**
* Mystery Person: simple, cartoon-style silhouetted outline of a person (does not vary by email)
*/
data object MysteryPerson : Predefined("mp")

/**
* 404: Fallback to a 404 error instead of returning a default image. This allows to detect if the user doesn't
* have a Gravatar image
*/
data object Status404 : Predefined("404")

/**
* Identicon: a geometric pattern based on an email hash
*/
data object Identicon : Predefined("identicon")

/**
* Monster: a generated "monster" with different colors, faces, etc
*/
data object Monster : Predefined("monsterid")

/**
* Wavatar: generated faces with differing features and backgrounds
*/
data object Wavatar : Predefined("wavatar")

/**
* Retro: awesome generated, 8-bit arcade-style pixelated faces
*/
data object Retro : Predefined("retro")

/**
* Blank: a transparent PNG image
*/
data object Blank : Predefined("blank")

/**
* Robohash: a generated robot with different colors, faces, etc
*/
data object Robohash : Predefined("robohash")

/**
* @param defaultImageUrl the custom url to use as the default avatar image.
* If you prefer to use your own default image (perhaps your logo, a funny face, whatever), then you can
* easily do so by using the CustomUrl option and supplying the URL to an image.
*
* Rating and size parameters are ignored when the custom default is set.
* There are a few conditions which must be met for default image URL:
* - MUST be publicly available (e.g. cannot be on an intranet, on a local development machine,
* behind HTTP Auth or some other firewall etc). Default images are passed through a security
* scan to avoid malicious content.
* - MUST be accessible via HTTP or HTTPS on the standard ports, 80 and 443, respectively.
* - MUST have a recognizable image extension (jpg, jpeg, gif, png, heic)
* - MUST NOT include a querystring (if it does, it will be ignored)
*
* @param defaultImageUrl the custom url to use as the default avatar image.
*/
data class CustomUrl(val defaultImageUrl: String) : DefaultAvatarImage()

/**
* Get the query parameter for the default avatar image depending on the type of default avatar image.
*
* @return the query parameter
*/
fun queryParam(): String = when (this) {
is Predefined -> style
is CustomUrl -> defaultImageUrl
Expand Down
29 changes: 29 additions & 0 deletions gravatar/src/main/java/com/gravatar/GravatarApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,33 @@ class GravatarApi(private val okHttpClient: OkHttpClient? = null) {
const val LOG_TAG = "Gravatar"
}

/**
* Error types for Gravatar image upload
*/
enum class ErrorType {
/** server returned an error */
SERVER,

/** network request timed out */
TIMEOUT,

/** network is not available */
NETWORK,

/** An unknown error occurred */
UNKNOWN,
}

val coroutineScope = CoroutineScope(GravatarSdkDI.dispatcherDefault)

/**
* Uploads a Gravatar image for the given email address.
*
* @param file The image file to upload
* @param email The email address to associate the image with
* @param accessToken The bearer token for the user's WordPress/Gravatar account
* @param gravatarUploadListener The listener to notify of the upload result
*/
fun uploadGravatar(
file: File,
email: String,
Expand Down Expand Up @@ -78,9 +96,20 @@ class GravatarApi(private val okHttpClient: OkHttpClient? = null) {
)
}

/**
* Listener for Gravatar image upload
*/
interface GravatarUploadListener {
/**
* Called when the Gravatar image upload is successful
*/
fun onSuccess()

/**
* Called when the Gravatar image upload fails
*
* @param errorType The type of error that occurred
*/
fun onError(errorType: ErrorType)
}
}
18 changes: 14 additions & 4 deletions gravatar/src/main/java/com/gravatar/GravatarConstants.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package com.gravatar

object GravatarConstants {
// Gravatar image / avatar
/** Gravatar image base URL */
const val GRAVATAR_IMAGE_BASE_URL = "https://www.gravatar.com/"

/** Gravatar image host */
const val GRAVATAR_IMAGE_HOST = "www.gravatar.com"

/** Gravatar image path */
const val GRAVATAR_IMAGE_PATH = "avatar"
const val GRAVATAR_IMAGE_RAW_HOST = "gravatar.com"

// Gravatar API
/** Gravatar image base host */
const val GRAVATAR_IMAGE_BASE_HOST = "gravatar.com"

/** Gravatar API base URL */
const val GRAVATAR_API_BASE_URL = "https://api.gravatar.com/v1/"

// Minimum and maximum size of the avatar
/** Minimum size of the avatar */
const val MINIMUM_AVATAR_SIZE = 1

/** Maximum size of the avatar */
const val MAXIMUM_AVATAR_SIZE = 2048

/** Range of the avatar size */
val AVATAR_SIZE_RANGE = MINIMUM_AVATAR_SIZE..MAXIMUM_AVATAR_SIZE
}
58 changes: 54 additions & 4 deletions gravatar/src/main/java/com/gravatar/GravatarUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package com.gravatar

import android.net.Uri
import com.gravatar.GravatarConstants.AVATAR_SIZE_RANGE
import com.gravatar.GravatarConstants.GRAVATAR_IMAGE_BASE_HOST
import com.gravatar.GravatarConstants.GRAVATAR_IMAGE_HOST
import com.gravatar.GravatarConstants.GRAVATAR_IMAGE_PATH
import com.gravatar.GravatarConstants.GRAVATAR_IMAGE_RAW_HOST
import java.security.MessageDigest

/**
* Convert a byte array to a hexadecimal string.
*/
private fun ByteArray.toHex(): String {
return joinToString("") { "%02x".format(it) }
}
Expand All @@ -26,14 +29,37 @@ private fun Uri.Builder.appendGravatarQueryParameters(
}
}

/**
* Hash a string using SHA-256.
*
* @return SHA-256 hash as a hexadecimal string
*/
fun String.sha256Hash(): String {
return MessageDigest.getInstance("SHA-256").digest(this.toByteArray()).toHex()
}

/**
* Generate a Gravatar hash the for a given email address.
*
* @param email Email address
*
* @return hash that can used to address Gravatar images or profiles
*/
fun emailAddressToGravatarHash(email: String): String {
return email.trim().lowercase().sha256Hash()
}

/**
* Generate Gravatar URL for the given email address.
*
* @param email Email address
* @param size Size of the avatar, must be between 1 and 2048. Optional: default to 80
* @param defaultAvatarImage Default avatar image. Optional: default to Gravatar logo
* @param rating Image rating. Optional: default to General, suitable for display on all websites with any audience
* @param forceDefaultAvatarImage Force default avatar image. Optional: default to false
*
* @return Gravatar URL
*/
fun emailAddressToGravatarUrl(
email: String,
size: Int? = null,
Expand All @@ -44,6 +70,17 @@ fun emailAddressToGravatarUrl(
return emailAddressToGravatarUri(email, size, defaultAvatarImage, rating, forceDefaultAvatarImage).toString()
}

/**
* Generate Gravatar Uri for the given email address.
*
* @param email Email address
* @param size Size of the avatar, must be between 1 and 2048. Optional: default to 80
* @param defaultAvatarImage Default avatar image. Optional: default to Gravatar logo
* @param rating Image rating. Optional: default to General, suitable for display on all websites with any audience
* @param forceDefaultAvatarImage Force default avatar image. Optional: default to false
*
* @return Gravatar Uri
*/
fun emailAddressToGravatarUri(
email: String,
size: Int? = null,
Expand All @@ -60,6 +97,17 @@ fun emailAddressToGravatarUri(
.build()
}

/**
* Rewrite Gravatar URL to use different options. Keep only the path and hash.
*
* @param url Gravatar URL
* @param size Size of the avatar, must be between 1 and 2048. Optional: default to 80
* @param defaultAvatarImage Default avatar image. Optional: default to Gravatar logo
* @param rating Image rating. Optional: default to General, suitable for display on all websites with any audience
* @param forceDefaultAvatarImage Force default avatar image. Optional: default to false
*
* @return Gravatar URL with updated query parameters
*/
fun rewriteGravatarImageUrlQueryParams(
url: String,
size: Int? = null,
Expand All @@ -71,13 +119,15 @@ fun rewriteGravatarImageUrlQueryParams(
}

/**
* Rewrite gravatar URL to use different options. Keep only the path and hash.
* Rewrite Gravatar URL to use different options. Keep only the path and hash.
*
* @param url Gravatar URL
* @param size Size of the avatar, must be between 1 and 2048. Optional: default to 80
* @param defaultAvatarImage Default avatar image. Optional: default to gravatar logo
* @param defaultAvatarImage Default avatar image. Optional: default to Gravatar logo
* @param rating Image rating. Optional: default to General, suitable for display on all websites with any audience
* @param forceDefaultAvatarImage Force default avatar image. Optional: default to false
*
* @return Gravatar Uri with updated query parameters
*/
fun rewriteGravatarImageUriQueryParams(
url: String,
Expand All @@ -87,7 +137,7 @@ fun rewriteGravatarImageUriQueryParams(
forceDefaultAvatarImage: Boolean? = null,
): Uri {
val uri = Uri.parse(url)
require(uri.host?.contains(GRAVATAR_IMAGE_RAW_HOST, true) ?: false) { "Not a gravatar URL: ${uri.host}" }
require(uri.host?.contains(GRAVATAR_IMAGE_BASE_HOST, true) ?: false) { "Not a Gravatar URL: ${uri.host}" }
return Uri.Builder()
.scheme(uri.scheme)
.authority(uri.host)
Expand Down
5 changes: 3 additions & 2 deletions gravatar/src/main/java/com/gravatar/HttpResponseCode.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.gravatar

object HttpResponseCode {
// 4xx codes
/** HTTP client timeout code */
const val HTTP_CLIENT_TIMEOUT = 408

// 5xx codes
private const val HTTP_INTERNAL_ERROR = 500
private const val NETWORK_CONNECT_TIMEOUT_ERROR = 599

/** Server error codes (5xx) */
val SERVER_ERRORS = HTTP_INTERNAL_ERROR..NETWORK_CONNECT_TIMEOUT_ERROR
}
Loading