Lint native files (#4768)

zio/stable
Hailey 2024-07-11 18:15:35 -07:00 committed by GitHub
parent b433469ab9
commit 2397104ad6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 393 additions and 375 deletions

2
.editorconfig 100644
View File

@ -0,0 +1,2 @@
[*.{kt,kts}]
indent_size=2

View File

@ -30,12 +30,11 @@ class ShareViewController: UIViewController {
}
}
private func handleText(item: NSItemProvider) async -> Void {
private func handleText(item: NSItemProvider) async {
do {
if let data = try await item.loadItem(forTypeIdentifier: "public.text") as? String {
if let encoded = data.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)")
{
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") {
_ = self.openURL(url)
}
}
@ -45,12 +44,11 @@ class ShareViewController: UIViewController {
}
}
private func handleUrl(item: NSItemProvider) async -> Void {
private func handleUrl(item: NSItemProvider) async {
do {
if let data = try await item.loadItem(forTypeIdentifier: "public.url") as? URL {
if let encoded = data.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)")
{
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") {
_ = self.openURL(url)
}
}
@ -60,7 +58,7 @@ class ShareViewController: UIViewController {
}
}
private func handleImages(items: [NSItemProvider]) async -> Void {
private func handleImages(items: [NSItemProvider]) async {
let firstFourItems: [NSItemProvider]
if items.count < 4 {
firstFourItems = items
@ -72,7 +70,7 @@ class ShareViewController: UIViewController {
var imageUris = ""
for (index, item) in firstFourItems.enumerated() {
var imageUriInfo: String? = nil
var imageUriInfo: String?
do {
if let dataUri = try await item.loadItem(forTypeIdentifier: "public.image") as? URL {
@ -100,8 +98,7 @@ class ShareViewController: UIViewController {
if valid,
let encoded = imageUris.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?imageUris=\(encoded)")
{
let url = URL(string: "\(self.appScheme)://intent/compose?imageUris=\(encoded)") {
_ = self.openURL(url)
}
@ -119,13 +116,11 @@ class ShareViewController: UIViewController {
// extension does.
if let dir = FileManager()
.containerURL(
forSecurityApplicationGroupIdentifier: "group.app.bsky")
{
forSecurityApplicationGroupIdentifier: "group.app.bsky") {
let filePath = "\(dir.absoluteString)\(ProcessInfo.processInfo.globallyUniqueString).jpeg"
if let newUri = URL(string: filePath),
let jpegData = image.jpegData(compressionQuality: 1)
{
let jpegData = image.jpegData(compressionQuality: 1) {
try jpegData.write(to: newUri)
return "\(newUri.absoluteString)|\(image.size.width)|\(image.size.height)"
}
@ -136,7 +131,7 @@ class ShareViewController: UIViewController {
}
}
private func completeRequest() -> Void {
private func completeRequest() {
self.extensionContext?.completeRequest(returningItems: nil)
}

View File

@ -5,7 +5,7 @@ import com.google.firebase.messaging.RemoteMessage
class BackgroundNotificationHandler(
private val context: Context,
private val notifInterface: BackgroundNotificationHandlerInterface
private val notifInterface: BackgroundNotificationHandlerInterface,
) {
fun handleMessage(remoteMessage: RemoteMessage) {
if (ExpoBackgroundNotificationHandlerModule.isForegrounded) {

View File

@ -8,7 +8,8 @@ class ExpoBackgroundNotificationHandlerModule : Module() {
var isForegrounded = false
}
override fun definition() = ModuleDefinition {
override fun definition() =
ModuleDefinition {
Name("ExpoBackgroundNotificationHandler")
OnCreate {

View File

@ -2,7 +2,8 @@ package expo.modules.backgroundnotificationhandler
import android.content.Context
val DEFAULTS = mapOf<String, Any>(
val DEFAULTS =
mapOf<String, Any>(
"playSoundChat" to true,
"playSoundFollow" to false,
"playSoundLike" to false,
@ -10,11 +11,14 @@ val DEFAULTS = mapOf<String, Any>(
"playSoundQuote" to false,
"playSoundReply" to false,
"playSoundRepost" to false,
"mutedThreads" to mapOf<String, List<String>>()
)
"mutedThreads" to mapOf<String, List<String>>(),
)
class NotificationPrefs (private val context: Context?) {
private val prefs = context?.getSharedPreferences("xyz.blueskyweb.app", Context.MODE_PRIVATE)
class NotificationPrefs(
private val context: Context?,
) {
private val prefs =
context?.getSharedPreferences("xyz.blueskyweb.app", Context.MODE_PRIVATE)
?: throw Error("Context is null")
fun initialize() {
@ -41,94 +45,99 @@ class NotificationPrefs (private val context: Context?) {
}
}
}
}
.apply()
}.apply()
}
fun getAllPrefs(): MutableMap<String, *> {
return prefs.all
}
fun getAllPrefs(): MutableMap<String, *> = prefs.all
fun getBoolean(key: String): Boolean {
return prefs.getBoolean(key, false)
}
fun getBoolean(key: String): Boolean = prefs.getBoolean(key, false)
fun getString(key: String): String? {
return prefs.getString(key, null)
}
fun getString(key: String): String? = prefs.getString(key, null)
fun getStringArray(key: String): Array<String>? {
return prefs.getStringSet(key, null)?.toTypedArray()
}
fun getStringArray(key: String): Array<String>? = prefs.getStringSet(key, null)?.toTypedArray()
fun setBoolean(key: String, value: Boolean) {
fun setBoolean(
key: String,
value: Boolean,
) {
prefs
.edit()
.apply {
putBoolean(key, value)
}
.apply()
}.apply()
}
fun setString(key: String, value: String) {
fun setString(
key: String,
value: String,
) {
prefs
.edit()
.apply {
putString(key, value)
}
.apply()
}.apply()
}
fun setStringArray(key: String, value: Array<String>) {
fun setStringArray(
key: String,
value: Array<String>,
) {
prefs
.edit()
.apply {
putStringSet(key, value.toSet())
}
.apply()
}.apply()
}
fun addToStringArray(key: String, string: String) {
fun addToStringArray(
key: String,
string: String,
) {
prefs
.edit()
.apply {
val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf()
set.add(string)
putStringSet(key, set)
}
.apply()
}.apply()
}
fun removeFromStringArray(key: String, string: String) {
fun removeFromStringArray(
key: String,
string: String,
) {
prefs
.edit()
.apply {
val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf()
set.remove(string)
putStringSet(key, set)
}
.apply()
}.apply()
}
fun addManyToStringArray(key: String, strings: Array<String>) {
fun addManyToStringArray(
key: String,
strings: Array<String>,
) {
prefs
.edit()
.apply {
val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf()
set.addAll(strings.toSet())
putStringSet(key, set)
}
.apply()
}.apply()
}
fun removeManyFromStringArray(key: String, strings: Array<String>) {
fun removeManyFromStringArray(
key: String,
strings: Array<String>,
) {
prefs
.edit()
.apply {
val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf()
set.removeAll(strings.toSet())
putStringSet(key, set)
}
.apply()
}.apply()
}
}

View File

@ -2,16 +2,16 @@ import ExpoModulesCore
let APP_GROUP = "group.app.bsky"
let DEFAULTS: [String:Any] = [
"playSoundChat" : true,
let DEFAULTS: [String: Any] = [
"playSoundChat": true,
"playSoundFollow": false,
"playSoundLike": false,
"playSoundMention": false,
"playSoundQuote": false,
"playSoundReply": false,
"playSoundRepost": false,
"mutedThreads": [:] as! [String:[String]],
"badgeCount": 0,
"mutedThreads": [:] as! [String: [String]],
"badgeCount": 0
]
/*
@ -35,7 +35,7 @@ public class ExpoBackgroundNotificationHandlerModule: Module {
}
}
AsyncFunction("getAllPrefsAsync") { () -> [String:Any]? in
AsyncFunction("getAllPrefsAsync") { () -> [String: Any]? in
var keys: [String] = []
DEFAULTS.forEach { p in
keys.append(p.key)
@ -64,22 +64,21 @@ public class ExpoBackgroundNotificationHandlerModule: Module {
return nil
}
AsyncFunction("setBoolAsync") { (forKey: String, value: Bool) -> Void in
AsyncFunction("setBoolAsync") { (forKey: String, value: Bool) in
userDefaults?.setValue(value, forKey: forKey)
}
AsyncFunction("setStringAsync") { (forKey: String, value: String) -> Void in
AsyncFunction("setStringAsync") { (forKey: String, value: String) in
userDefaults?.setValue(value, forKey: forKey)
}
AsyncFunction("setStringArrayAsync") { (forKey: String, value: [String]) -> Void in
AsyncFunction("setStringArrayAsync") { (forKey: String, value: [String]) in
userDefaults?.setValue(value, forKey: forKey)
}
AsyncFunction("addToStringArrayAsync") { (forKey: String, string: String) in
if var curr = userDefaults?.stringArray(forKey: forKey),
!curr.contains(string)
{
!curr.contains(string) {
curr.append(string)
userDefaults?.setValue(curr, forKey: forKey)
}

View File

@ -5,7 +5,10 @@ import android.graphics.Canvas
import android.graphics.drawable.Animatable
import androidx.appcompat.widget.AppCompatImageView
class AppCompatImageViewExtended(context: Context, private val parent: GifView): AppCompatImageView(context) {
class AppCompatImageViewExtended(
context: Context,
private val parent: GifView,
) : AppCompatImageView(context) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

View File

@ -6,7 +6,8 @@ import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoBlueskyGifViewModule : Module() {
override fun definition() = ModuleDefinition {
override fun definition() =
ModuleDefinition {
Name("ExpoBlueskyGifView")
AsyncFunction("prefetchAsync") { sources: List<String> ->
@ -23,7 +24,7 @@ class ExpoBlueskyGifViewModule : Module() {
View(GifView::class) {
Events(
"onPlayerStateChange"
"onPlayerStateChange",
)
Prop("source") { view: GifView, source: String ->

View File

@ -1,6 +1,5 @@
package expo.modules.blueskygifview
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Animatable
@ -15,7 +14,10 @@ import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.views.ExpoView
class GifView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
class GifView(
context: Context,
appContext: AppContext,
) : ExpoView(context, appContext) {
// Events
private val onPlayerStateChange by EventDispatcher()
@ -44,8 +46,7 @@ class GifView(context: Context, appContext: AppContext) : ExpoView(context, appC
}
}
//<editor-fold desc="Lifecycle">
// <editor-fold desc="Lifecycle">
init {
this.setBackgroundColor(Color.TRANSPARENT)
@ -70,25 +71,28 @@ class GifView(context: Context, appContext: AppContext) : ExpoView(context, appC
super.onDetachedFromWindow()
}
//</editor-fold>
// </editor-fold>
//<editor-fold desc="Loading">
// <editor-fold desc="Loading">
private fun load() {
if (placeholderSource == null || source == null) {
return
}
this.webpRequest = glide.load(source)
this.webpRequest =
glide
.load(source)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.skipMemoryCache(false)
.listener(object: RequestListener<Drawable> {
.listener(
object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: com.bumptech.glide.load.DataSource?,
isFirstResource: Boolean
isFirstResource: Boolean,
): Boolean {
if (placeholderRequest != null) {
glide.clear(placeholderRequest)
@ -100,25 +104,26 @@ class GifView(context: Context, appContext: AppContext) : ExpoView(context, appC
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return true
}
})
.into(this.imageView)
isFirstResource: Boolean,
): Boolean = true
},
).into(this.imageView)
if (this.imageView.drawable == null || this.imageView.drawable !is Animatable) {
this.placeholderRequest = glide.load(placeholderSource)
this.placeholderRequest =
glide
.load(placeholderSource)
.diskCacheStrategy(DiskCacheStrategy.DATA)
// Let's not bloat the memory cache with placeholders
.skipMemoryCache(true)
.listener(object: RequestListener<Drawable> {
.listener(
object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: com.bumptech.glide.load.DataSource?,
isFirstResource: Boolean
isFirstResource: Boolean,
): Boolean {
// Incase this request finishes after the webp, let's just not set
// the drawable. This shouldn't happen because the request should get cancelled
@ -132,18 +137,16 @@ class GifView(context: Context, appContext: AppContext) : ExpoView(context, appC
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return true
}
})
.submit()
isFirstResource: Boolean,
): Boolean = true
},
).submit()
}
}
//</editor-fold>
// </editor-fold>
//<editor-fold desc="Controls">
// <editor-fold desc="Controls">
fun play() {
this.imageView.play()
@ -165,16 +168,18 @@ class GifView(context: Context, appContext: AppContext) : ExpoView(context, appC
}
}
//</editor-fold>
// </editor-fold>
//<editor-fold desc="Util">
// <editor-fold desc="Util">
fun firePlayerStateChange() {
onPlayerStateChange(mapOf(
onPlayerStateChange(
mapOf(
"isPlaying" to this.isPlaying,
"isLoaded" to this.isLoaded,
))
),
)
}
//</editor-fold>
// </editor-fold>
}

View File

@ -22,8 +22,8 @@ public class GifView: ExpoView, AVPlayerViewControllerDelegate {
private var placeholderOperation: SDWebImageCombinedOperation?
// Props
var source: String? = nil
var placeholderSource: String? = nil
var source: String?
var placeholderSource: String?
var autoplay = true {
didSet {
if !autoplay {
@ -78,8 +78,7 @@ public class GifView: ExpoView, AVPlayerViewControllerDelegate {
// See:
// https://github.com/SDWebImage/SDWebImage/blob/master/Docs/HowToUse.md#using-asynchronous-image-caching-independently
if !SDImageCache.shared.diskImageDataExists(withKey: source),
let url = URL(string: placeholderSource)
{
let url = URL(string: placeholderSource) {
self.placeholderOperation = imageManager.loadImage(
with: url,
options: [.retryFailed],
@ -132,8 +131,7 @@ public class GifView: ExpoView, AVPlayerViewControllerDelegate {
if let placeholderSource = self.placeholderSource,
imageUrl?.absoluteString == placeholderSource,
self.imageView.image == nil,
let image = image
{
let image = image {
self.setImage(image)
return
}
@ -142,8 +140,7 @@ public class GifView: ExpoView, AVPlayerViewControllerDelegate {
imageUrl?.absoluteString == source,
// UIImage perf suckssss if the image is animated
let data = data,
let animatedImage = SDAnimatedImage(data: data)
{
let animatedImage = SDAnimatedImage(data: data) {
self.placeholderOperation?.cancel()
self.isPlaying = self.autoplay
self.isLoaded = true

View File

@ -4,7 +4,8 @@ import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoBlueskyDevicePrefsModule : Module() {
override fun definition() = ModuleDefinition {
override fun definition() =
ModuleDefinition {
Name("ExpoBlueskyDevicePrefs")
}
}

View File

@ -3,17 +3,19 @@ package expo.modules.blueskyswissarmy.referrer
import android.util.Log
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
import expo.modules.kotlin.Promise
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.Promise
class ExpoBlueskyReferrerModule : Module() {
override fun definition() = ModuleDefinition {
override fun definition() =
ModuleDefinition {
Name("ExpoBlueskyReferrer")
AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise ->
val referrerClient = InstallReferrerClient.newBuilder(appContext.reactContext).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
referrerClient.startConnection(
object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
Log.d("ExpoGooglePlayReferrer", "Successfully retrieved referrer info.")
@ -25,15 +27,15 @@ class ExpoBlueskyReferrerModule : Module() {
mapOf(
"installReferrer" to response.installReferrer,
"clickTimestamp" to response.referrerClickTimestampSeconds,
"installTimestamp" to response.installBeginTimestampSeconds
)
"installTimestamp" to response.installBeginTimestampSeconds,
),
)
} else {
Log.d("ExpoGooglePlayReferrer", "Failed to get referrer info. Unknown error.")
promise.reject(
"ERR_GOOGLE_PLAY_REFERRER_UNKNOWN",
"Failed to get referrer info",
Exception("Failed to get referrer info")
Exception("Failed to get referrer info"),
)
}
referrerClient.endConnection()
@ -45,10 +47,11 @@ class ExpoBlueskyReferrerModule : Module() {
promise.reject(
"ERR_GOOGLE_PLAY_REFERRER_DISCONNECTED",
"Failed to get referrer info",
Exception("Failed to get referrer info")
Exception("Failed to get referrer info"),
)
}
})
},
)
}
}
}

View File

@ -13,7 +13,8 @@ import java.io.FileOutputStream
import java.net.URLEncoder
class ExpoReceiveAndroidIntentsModule : Module() {
override fun definition() = ModuleDefinition {
override fun definition() =
ModuleDefinition {
Name("ExpoReceiveAndroidIntents")
OnNewIntent {
@ -22,7 +23,7 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}
private fun handleIntent(intent: Intent?) {
if(appContext.currentActivity == null || intent == null) return
if (appContext.currentActivity == null || intent == null) return
if (intent.action == Intent.ACTION_SEND) {
if (intent.type == "text/plain") {
@ -40,7 +41,7 @@ class ExpoReceiveAndroidIntentsModule : Module() {
private fun handleTextIntent(intent: Intent) {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
val encoded = URLEncoder.encode(it, "UTF-8")
"bluesky://intent/compose?text=${encoded}".toUri().let { uri ->
"bluesky://intent/compose?text=$encoded".toUri().let { uri ->
val newIntent = Intent(Intent.ACTION_VIEW, uri)
appContext.currentActivity?.startActivity(newIntent)
}
@ -48,7 +49,8 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}
private fun handleImageIntent(intent: Intent) {
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
} else {
intent.getParcelableExtra(Intent.EXTRA_STREAM)
@ -76,16 +78,16 @@ class ExpoReceiveAndroidIntentsModule : Module() {
uris.forEachIndexed { index, uri ->
val info = getImageInfo(uri)
val params = buildUriData(info)
allParams = "${allParams}${params}"
allParams = "${allParams}$params"
if (index < uris.count() - 1) {
allParams = "${allParams},"
allParams = "$allParams,"
}
}
val encoded = URLEncoder.encode(allParams, "UTF-8")
"bluesky://intent/compose?imageUris=${encoded}".toUri().let {
"bluesky://intent/compose?imageUris=$encoded".toUri().let {
val newIntent = Intent(Intent.ACTION_VIEW, it)
appContext.currentActivity?.startActivity(newIntent)
}
@ -104,7 +106,7 @@ class ExpoReceiveAndroidIntentsModule : Module() {
return mapOf(
"width" to bitmap.width,
"height" to bitmap.height,
"path" to file.path.toString()
"path" to file.path.toString(),
)
}
@ -114,6 +116,6 @@ class ExpoReceiveAndroidIntentsModule : Module() {
val path = info.getValue("path")
val width = info.getValue("width")
val height = info.getValue("height")
return "file://${path}|${width}|${height}"
return "file://$path|$width|$height"
}
}

View File

@ -35,7 +35,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
self.cancelGestureRecognizers = [lpg, tg]
}
// We don't want to recognize the scroll pan gesture and the swipe back gesture together
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer, otherGestureRecognizer is UIPanGestureRecognizer {
@ -64,11 +63,11 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
// This will be used to cancel the animation whenever we press inside of the scroll view. We don't want to change
// the scroll view gesture's delegate, so we add an additional recognizer to detect this.
@IBAction func callOnPress(_ sender: UITapGestureRecognizer) -> Void {
@IBAction func callOnPress(_ sender: UITapGestureRecognizer) {
self.stopTimer()
}
@IBAction func callOnPan(_ sender: UIPanGestureRecognizer) -> Void {
@IBAction func callOnPan(_ sender: UIPanGestureRecognizer) {
guard let rctsv = self.rctScrollView, let sv = rctsv.scrollView else {
return
}
@ -129,7 +128,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
}
var animTranslation = -translation
self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { timer in
self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { _ in
velocity *= 0.9875
animTranslation = (-velocity / 120) + animTranslation
@ -190,7 +189,6 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
}
}
func enableCancelGestureRecognizers() {
self.cancelGestureRecognizers?.forEach { r in
r.isEnabled = true
@ -203,11 +201,11 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
}
}
func scrollToOffset(_ offset: Int, animated: Bool = true) -> Void {
func scrollToOffset(_ offset: Int, animated: Bool = true) {
self.rctScrollView?.scroll(toOffset: CGPoint(x: 0, y: offset), animated: animated)
}
func stopTimer() -> Void {
func stopTimer() {
self.disableCancelGestureRecognizers()
self.animTimer?.invalidate()
self.animTimer = nil

View File

@ -29,6 +29,8 @@
"test-ci": "NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit",
"test-coverage": "NODE_ENV=test jest --coverage",
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src",
"lint-native": "swiftlint ./modules && ktlint ./modules",
"lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules",
"typecheck": "tsc --project ./tsconfig.check.json",
"e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node --project tsconfig.e2e.json __e2e__/mock-server.ts",
"e2e:metro": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",