Skip to content

Commit

Permalink
ci: added detekt for kotlin static analysis (#199)
Browse files Browse the repository at this point in the history
## 馃摐 Description

Added `detekt` for static analysis of `kotlin` code.

## 馃挕 Motivation and Context

I wasn't satisfied with the quality of the `kotlin` code, because
sometimes I wrote big lines of the code, sometimes I wrote code-smells
and didn't know about it. With the usage of `detekt` all these issues
will be detected and reported.

I've added CI job to constantly run it whenever `kotlin` files were
changed.

Potentially `detekt` could be added as a `gradle` plugin and we could
run it during the build, but I decided to integrate it as a separate
tool, since all other linters/tools integrated in CI as separate tools
(so just decided to keep a consistency).

## 馃摙 Changelog

### Android
- resolved all issues reported by `detekt`;

## 馃 How Has This Been Tested?

Tested manually on Pixel 7 Pro (android) + verified that CI can be
passed.

## 馃摑 Checklist

- [x] CI successfully passed
  • Loading branch information
kirillzyusko committed Aug 11, 2023
1 parent da04d8c commit 756be07
Show file tree
Hide file tree
Showing 15 changed files with 274 additions and 132 deletions.
10 changes: 4 additions & 6 deletions .github/workflows/size-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ name: 馃И Size diff

on:
pull_request:
paths:
- '.github/workflows/size-diff.yml'
- '!example/**'
- '!FabricExample/**'
- '!docs/**'
- '!scripts/**'
paths-ignore:
- 'example/**'
- 'FabricExample/**'
- 'docs/**'

jobs:
check-diff:
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/verify-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,22 @@ jobs:
run: yarn install --frozen-lockfile --cwd ..
- name: Run Gradle Lint
run: ./gradlew assembleDebug
detekt:
runs-on: ubuntu-latest
name: 馃К Run detekt
defaults:
run:
working-directory: ./android
steps:
- uses: actions/checkout@v3
- name: Setup JDK 11
uses: actions/setup-java@v3
with:
distribution: 'microsoft'
java-version: '11'
- name: Install Detekt
run: |
curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip
unzip detekt-cli-1.23.1.zip
- name: Run Detekt
run: ./detekt-cli-1.23.1/bin/detekt-cli
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import com.facebook.react.views.view.ReactViewManager
import com.reactnativekeyboardcontroller.managers.KeyboardControllerViewManagerImpl
import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup

class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) : ReactViewManager(), KeyboardControllerViewManagerInterface<ReactViewGroup> {
class KeyboardControllerViewManager(mReactContext: ReactApplicationContext) :
ReactViewManager(),
KeyboardControllerViewManagerInterface<ReactViewGroup> {
private val manager = KeyboardControllerViewManagerImpl(mReactContext)
private val mDelegate = KeyboardControllerViewManagerDelegate(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package com.reactnativekeyboardcontroller
import com.facebook.react.bridge.ReactApplicationContext
import com.reactnativekeyboardcontroller.modules.StatusBarManagerCompatModuleImpl

class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationContext) : NativeStatusBarManagerCompatSpec(mReactContext) {
class StatusBarManagerCompatModule(mReactContext: ReactApplicationContext) :
NativeStatusBarManagerCompatSpec(mReactContext) {
private val module = StatusBarManagerCompatModuleImpl(mReactContext)

override fun getName(): String = StatusBarManagerCompatModuleImpl.NAME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
import com.reactnativekeyboardcontroller.extensions.dp
import kotlin.math.abs

private val TAG = KeyboardAnimationCallback::class.qualifiedName

class KeyboardAnimationCallback(
val view: ReactViewGroup,
val persistentInsetTypes: Int,
Expand All @@ -31,7 +33,6 @@ class KeyboardAnimationCallback(
val context: ThemedReactContext?,
val onApplyWindowInsetsListener: OnApplyWindowInsetsListener,
) : WindowInsetsAnimationCompat.Callback(dispatchMode), OnApplyWindowInsetsListener {
private val TAG = KeyboardAnimationCallback::class.qualifiedName
private var persistentKeyboardHeight = 0.0
private var isKeyboardVisible = false
private var isTransitioning = false
Expand All @@ -52,10 +53,29 @@ class KeyboardAnimationCallback(
if (this.isKeyboardVisible && oldFocus !== null) {
// imitate iOS behavior and send two instant start/end events containing an info about new tag
// 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
// 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be 100% included in
// onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time can bring breaking changes
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveStart", this.persistentKeyboardHeight, 1.0, 0, viewTagFocused))
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveEnd", this.persistentKeyboardHeight, 1.0, 0, viewTagFocused))
// 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
// 100% included in onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time
// can bring breaking changes
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveStart",
this.persistentKeyboardHeight,
1.0,
0,
viewTagFocused,
),
)
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveEnd",
this.persistentKeyboardHeight,
1.0,
0,
viewTagFocused,
),
)
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
}
Expand All @@ -80,28 +100,59 @@ class KeyboardAnimationCallback(
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
// when keyboard appears values will be (false && true)
// when keyboard disappears values will be (true && false)
// `!isTransitioning` check is needed to avoid calls of `onApplyWindowInsets` during keyboard animation
val isKeyboardShown = isKeyboardVisible && isKeyboardVisible()
// `isTransitioning` check is needed to avoid calls of `onApplyWindowInsets` during keyboard animation
// having such check allows us not to dispatch unnecessary incorrect events
// the condition will be executed only when keyboard is opened and changes its size
// (for example it happens when user changes keyboard type from 'text' to 'emoji' input
if (isKeyboardVisible && isKeyboardVisible() && !isTransitioning && Build.VERSION.SDK_INT >= 30 && !InteractiveKeyboardProvider.isInteractive) {
//
// `InteractiveKeyboardProvider.isInteractive` detect case when keyboard moves
// because of the gesture
val isMoving = isTransitioning || InteractiveKeyboardProvider.isInteractive
if (isKeyboardShown && !isMoving && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val keyboardHeight = getCurrentKeyboardHeight()
val durationL = 250L
val duration = durationL.toInt()
val duration = DEFAULT_ANIMATION_TIME.toInt()

this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveStart", keyboardHeight, 1.0, duration, viewTagFocused))
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveStart",
keyboardHeight,
1.0,
duration,
viewTagFocused,
),
)

val animation = ValueAnimator.ofFloat(this.persistentKeyboardHeight.toFloat(), keyboardHeight.toFloat())
animation.addUpdateListener { animator ->
val toValue = animator.animatedValue as Float
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMove", toValue.toDouble(), toValue.toDouble() / keyboardHeight, duration, viewTagFocused))
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMove",
toValue.toDouble(),
toValue.toDouble() / keyboardHeight,
duration,
viewTagFocused,
),
)
}
animation.doOnEnd {
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveEnd", keyboardHeight, 1.0, duration, viewTagFocused))
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveEnd",
keyboardHeight,
1.0,
duration,
viewTagFocused,
),
)
}
animation.setDuration(durationL).startDelay = 0
animation.setDuration(DEFAULT_ANIMATION_TIME).startDelay = 0
animation.start()

this.persistentKeyboardHeight = keyboardHeight
Expand All @@ -124,10 +175,22 @@ class KeyboardAnimationCallback(
this.persistentKeyboardHeight = keyboardHeight
}

this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow", getEventParams(keyboardHeight))
this.emitEvent(
"KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow",
getEventParams(keyboardHeight),
)

Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveStart", keyboardHeight, if (!isKeyboardVisible) 0.0 else 1.0, duration, viewTagFocused))
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveStart",
keyboardHeight,
if (!isKeyboardVisible) 0.0 else 1.0,
duration,
viewTagFocused,
),
)

return super.onStart(animation, bounds)
}
Expand Down Expand Up @@ -155,7 +218,8 @@ class KeyboardAnimationCallback(
try {
progress = abs((height / persistentKeyboardHeight)).let { if (it.isNaN()) 0.0 else it }
} catch (e: ArithmeticException) {
// do nothing, send progress as 0
// do nothing, just log an exception send progress as 0
Log.w(TAG, "Caught arithmetic exception during `progress` calculation: $e")
}
Log.i(TAG, "DiffY: $diffY $height $progress ${InteractiveKeyboardProvider.isInteractive} $viewTagFocused")

Expand Down Expand Up @@ -185,8 +249,20 @@ class KeyboardAnimationCallback(
}
isKeyboardVisible = isKeyboardVisible || isKeyboardShown

this.emitEvent("KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow", getEventParams(keyboardHeight))
this.sendEventToJS(KeyboardTransitionEvent(view.id, "topKeyboardMoveEnd", keyboardHeight, if (!isKeyboardVisible) 0.0 else 1.0, duration, viewTagFocused))
this.emitEvent(
"KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
getEventParams(keyboardHeight),
)
this.sendEventToJS(
KeyboardTransitionEvent(
view.id,
"topKeyboardMoveEnd",
keyboardHeight,
if (!isKeyboardVisible) 0.0 else 1.0,
duration,
viewTagFocused,
),
)

// reset to initial state
duration = 0
Expand Down Expand Up @@ -228,4 +304,8 @@ class KeyboardAnimationCallback(

return params
}

companion object {
private const val DEFAULT_ANIMATION_TIME = 250L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.dynamicanimation.animation.springAnimationOf
import androidx.dynamicanimation.animation.withSpringForceProperties
import kotlin.math.roundToInt

@Suppress("detekt:TooManyFunctions")
internal class KeyboardAnimationController {
private var insetsAnimationController: WindowInsetsAnimationControllerCompat? = null
private var pendingRequestCancellationSignal: CancellationSignal? = null
Expand Down Expand Up @@ -354,6 +355,7 @@ internal class KeyboardAnimationController {
visible: Boolean,
velocityY: Float? = null,
) {
@Suppress("detekt:UseCheckOrError")
val controller = insetsAnimationController
?: throw IllegalStateException("Controller should not be null")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class KeyboardTransitionEvent(
) : Event<KeyboardTransitionEvent>(viewId) {
override fun getEventName() = event

// TODO: All events for a given view can be coalesced?
// All events for a given view can be coalesced?
override fun getCoalescingKey(): Short = 0

override fun dispatch(rctEventEmitter: RCTEventEmitter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import com.facebook.react.common.MapBuilder
import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup

class KeyboardControllerViewManagerImpl(private val mReactContext: ReactApplicationContext) {
@Suppress("detekt:UnusedPrivateProperty")
class KeyboardControllerViewManagerImpl(mReactContext: ReactApplicationContext) {
fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup {
return EdgeToEdgeReactViewGroup(reactContext)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativekeyboardcontroller.views.KeyboardGestureAreaReactViewGroup

@Suppress("detekt:UnusedPrivateProperty")
class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext) {
fun createViewInstance(reactContext: ThemedReactContext): KeyboardGestureAreaReactViewGroup {
return KeyboardGestureAreaReactViewGroup(reactContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil

class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationContext) {
private val mDefaultMode: Int = mReactContext.currentActivity?.window?.attributes?.softInputMode ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
private val mDefaultMode: Int = mReactContext
.currentActivity
?.window
?.attributes
?.softInputMode
?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED

fun setInputMode(mode: Int) {
setSoftInputMode(mode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil

private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName

class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicationContext) {
private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName
private var controller: WindowInsetsControllerCompat? = null

fun setHidden(hidden: Boolean) {
Expand Down Expand Up @@ -41,14 +42,15 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
colorAnimation.addUpdateListener { animator ->
window.statusBarColor = animator.animatedValue as Int
}
colorAnimation.setDuration(300).startDelay = 0
colorAnimation.setDuration(DEFAULT_ANIMATION_TIME).startDelay = 0
colorAnimation.start()
} else {
window.statusBarColor = color
}
}
}

@Suppress("detekt:UnusedParameter")
fun setTranslucent(translucent: Boolean) {
// the status bar is translucent by default (once you wrapped App in Provider,
// and EdgeToEdgeReactViewGroup has been mounted and called
Expand All @@ -73,7 +75,10 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
if (this.controller == null) {
val activity = mReactContext.currentActivity
if (activity == null) {
Log.w(TAG, "StatusBarManagerCompatModule: can not get `WindowInsetsControllerCompat` because current activity is null.")
Log.w(
TAG,
"StatusBarManagerCompatModule: can not get `WindowInsetsControllerCompat` because current activity is null.",
)
return this.controller
}

Expand All @@ -87,5 +92,6 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati

companion object {
const val NAME = "StatusBarManagerCompat"
private const val DEFAULT_ANIMATION_TIME = 300L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import com.reactnativekeyboardcontroller.KeyboardAnimationCallback
import com.reactnativekeyboardcontroller.R
import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached

private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName

@SuppressLint("ViewConstructor")
class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
private var isStatusBarTranslucent = false
private var isNavigationBarTranslucent = false

Expand Down
Loading

0 comments on commit 756be07

Please sign in to comment.