Skip to content

Commit

Permalink
Merge pull request #20 from 4cc3ssX/main
Browse files Browse the repository at this point in the history
feat: custom color support + android custom progress color
  • Loading branch information
baronha authored Apr 2, 2024
2 parents ba2baaa + cf39de5 commit 198a73c
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 37 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ alert({
| `preset` | string | `done` | ![Both] ||| `done, error, none, spinner` |
| `duration` | number | `3` | ![Both] ||| The lifetime of the toast (seconds) |
| `titleColor` | string | null | ![Both] ||| |
| `shouldDismissByDrag` | boolean | true | ![Both] || | The behavior on `Android` is click |
| `shouldDismissByDrag` | boolean | true | ![Both] ||
| `position` | string | `top` | ![Both] || | **Toast** is displayed from `top` or `bottom` |
| `haptic` | string | null | ![iOS] || | `success, warning, error, none` |
| `shouldDismissByTap` | boolean | true | ![Both] | || |
Expand All @@ -177,8 +177,8 @@ alert({
- [x] `tintColor` for icon (v.1.0.5)
- [x] `backgroundColor` (v.1.0.3)
- [x] `spinner preset for toast` (v.1.1.0)
- [ ] custom color like StyleSheet (e.g. `#fff`, `red`, `rgba(0, 0, 0, .8)`)
- [ ] `shouldDismissByDrag` on **Android**
- [x] custom color like StyleSheet (e.g. `#fff`, `red`, `rgba(0, 0, 0, .8)`)
- [x] `shouldDismissByDrag` on **Android**
- [ ] `callback`


Expand Down
94 changes: 78 additions & 16 deletions android/src/main/java/com/ting/TingModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.GradientDrawable
import android.os.Handler
import android.os.Looper
import android.view.GestureDetector
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
Expand All @@ -25,6 +29,7 @@ import com.facebook.react.bridge.WritableMap
import com.hjq.window.EasyWindow
import java.io.IOException
import java.net.URL
import kotlin.math.abs
import kotlin.math.roundToInt


Expand All @@ -37,9 +42,9 @@ class TingModule internal constructor(context: ReactApplicationContext) : TingSp
private var alertOptionInit: ReadableMap? = null

@ReactMethod
override fun toast(RNOptions: ReadableMap) {
override fun toast(rnOptions: ReadableMap) {
// get container View
val options = toastOptionInit?.let { mergeMaps(it, RNOptions) } ?: RNOptions
val options = toastOptionInit?.let { mergeMaps(it, rnOptions) } ?: rnOptions

val container = getContainerView(R.layout.toast, options, "toast")
val duration: Int = getDuration(options)
Expand All @@ -65,19 +70,46 @@ class TingModule internal constructor(context: ReactApplicationContext) : TingSp
setYOffset(48)
setAnimStyle(toastAnim)
setOutsideTouchable(true)
setOnClickListener(R.id.toast,
EasyWindow.OnClickListener { toast: EasyWindow<*>, _: LinearLayout? ->
val shouldDismissByTap =
if (options.hasKey("shouldDismissByDrag")) options.getBoolean("shouldDismissByDrag") else true
if (shouldDismissByTap) toast.cancel()
})

if (options.hasKey("shouldDismissByDrag") && options.getBoolean("shouldDismissByDrag")) {
// Define dragThreshold in density-independent pixels (dp)
val dragThresholdDP = 12
val scale = context.resources.displayMetrics.density
val dragThreshold = (dragThresholdDP * scale + 0.5f).toInt()

// Add drag gesture recognizer
contentView?.let { contentView ->
val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
// Check if the user scrolls vertically and dismiss the toast window if needed
if (abs(distanceY) > abs(distanceX)) {
if (position == Gravity.TOP && distanceY > dragThreshold) { // Dismiss upward if toast is at the top
toastWindow.cancel()
return true
} else if (position == Gravity.BOTTOM && distanceY < -dragThreshold) { // Dismiss downward if toast is at the bottom
toastWindow.cancel()
return true
}
}

return super.onScroll(e1, e2, distanceX, distanceY)
}
})

contentView.setOnTouchListener(fun(_: View, event: MotionEvent): Boolean {
gestureDetector.onTouchEvent(event)
return true // Consume the touch event
})
}
}

}.show()
}
}

@ReactMethod
override fun alert(RNOptions: ReadableMap) {
val options = alertOptionInit?.let { mergeMaps(it, RNOptions) } ?: RNOptions
override fun alert(rnOptions: ReadableMap) {
val options = alertOptionInit?.let { mergeMaps(it, rnOptions) } ?: rnOptions

val container = getContainerView(R.layout.alert, options, "alert")
val duration: Int = getDuration(options)
Expand Down Expand Up @@ -147,6 +179,7 @@ class TingModule internal constructor(context: ReactApplicationContext) : TingSp
val titleColor = options?.getString("titleColor")
val message = options?.getString("message")
val messageColor = options?.getString("messageColor")
val progressColor = options?.getString("progressColor")
val preset = options?.getString("preset")
val backgroundColor = options?.getString("backgroundColor")
val borderRadius = if (options.hasKey("borderRadius")) options?.getInt("borderRadius") else null
Expand Down Expand Up @@ -213,6 +246,13 @@ class TingModule internal constructor(context: ReactApplicationContext) : TingSp
iconView.visibility = ImageView.GONE
progressBar.id = R.id.loading_spinner
progressBar.layoutParams = progressSize

if (progressColor != null) {
val progressDrawable = progressBar.indeterminateDrawable.mutate()
progressDrawable.colorFilter = PorterDuffColorFilter(parseColor(progressColor), PorterDuff.Mode.SRC_IN)
progressBar.indeterminateDrawable = progressDrawable
}

container.addView(progressBar, 0)
}

Expand Down Expand Up @@ -271,14 +311,36 @@ fun isNumber(value: Any?): Boolean {
return value != null && (value is Int || value is Long || value is Float || value is Double)
}

fun parseColor(hexColor: String): Int {
fun parseColor(colorString: String): Int {
val fallbackColor = Color.BLACK
val color = try {
Color.parseColor(hexColor)
} catch (e: IllegalArgumentException) {
fallbackColor
// Try parsing color as hex
if (colorString.startsWith("#")) {
return Color.parseColor(colorString)
}
return color

// Try parsing color as named color
val namedColor = try {
Color::class.java.getField(colorString.uppercase()).get(null) as Int
} catch (e: Exception) {
null
}
if (namedColor != null) {
return namedColor
}

// Try parsing color as RGB or RGBA
val rgbRegex = Regex("""rgba?\((\d{1,3}), (\d{1,3}), (\d{1,3})(, (\d(\.\d)?))?\)""")
val rgbMatchResult = rgbRegex.matchEntire(colorString)
if (rgbMatchResult != null) {
val red = rgbMatchResult.groups[1]?.value?.toIntOrNull() ?: return fallbackColor
val green = rgbMatchResult.groups[2]?.value?.toIntOrNull() ?: return fallbackColor
val blue = rgbMatchResult.groups[3]?.value?.toIntOrNull() ?: return fallbackColor
val alpha = if (colorString.startsWith("rgb(")) 1.0f else rgbMatchResult.groups[5]?.value?.toFloatOrNull() ?: 1.0f
return Color.argb((alpha * 255).toInt(), red, green, blue)
}

// Return fallback color if parsing fails
return fallbackColor
}

fun convertInt2Size(number: Int?): Int {
Expand Down
4 changes: 3 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ const DATA: ItemType[] = [
onPress: () =>
toast({
duration: 10,

title: 'Việt Nam',
titleColor: '#ffffff',
message: 'Vietnamese Gangz 🇻🇳',
Expand Down Expand Up @@ -194,6 +193,8 @@ const DATA: ItemType[] = [
title: 'Đáy xã hội',
message: 'Chờ xíu',
preset: 'spinner',
progressColor: '#FD966A',
shouldDismissByDrag: true,
}),
},
{
Expand Down Expand Up @@ -254,6 +255,7 @@ const DATA: ItemType[] = [
blurBackdrop: 20,
backdropOpacity: 0.1,
preset: 'spinner',
progressColor: '#FD966A',
}),
},
];
Expand Down
8 changes: 4 additions & 4 deletions ios/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ class Options {
self.title = option["title"] as? String ?? "Title"
self.message = option["message"] as? String
self.duration = option["duration"] as? TimeInterval ?? 3
self.backgroundColor = UIColor.hexStringToColor(option["backgroundColor"] as? String)
self.backgroundColor = UIColor.parseColor(option["backgroundColor"] as? String)

if let messageColor = option["messageColor"] as? String {
self.messageColor = UIColor.hexStringToColor(messageColor)
self.messageColor = UIColor.parseColor(messageColor)
}

if let titleColor = option["titleColor"] as? String {
self.titleColor = UIColor.hexStringToColor(titleColor)
self.titleColor = UIColor.parseColor(titleColor)
}

// custom icon
Expand All @@ -53,7 +53,7 @@ class Options {
func getCustomIcon(icon: NSDictionary) -> UIImage? {
if let iconURI = icon["uri"] as? String {
if let iconImage = getImage(icon: iconURI) {
let color = UIColor.hexStringToColor(icon["tintColor"] as? String)
let color = UIColor.parseColor(icon["tintColor"] as? String)
if(color != nil){
return iconImage.getTintColor(color!)
}
Expand Down
57 changes: 44 additions & 13 deletions ios/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,57 @@ extension UIImage {
}

extension UIColor {
static func hexStringToColor(_ stringToConvert: String?) -> UIColor? {
if stringToConvert == nil {
static func parseColor(_ colorString: String?) -> UIColor? {
guard let colorString = colorString else {
return nil
}

let noHashString = stringToConvert!.replacingOccurrences(of: "#", with: "")

// Đảm bảo độ dài chuỗi hợp lệ
guard noHashString.count == 6 else {
return nil
// Try parsing color as hex
if colorString.hasPrefix("#") {
let hexString = String(colorString.dropFirst()) // Remove '#' character

// Check if the hex string has valid length
guard hexString.count == 6 || hexString.count == 8 else {
return .gray // Return gray color for invalid hex string
}

// Parse hex values
var rgbValue: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&rgbValue)

// Extract individual color components
let red = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0
let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0
let blue = CGFloat(rgbValue & 0x0000FF) / 255.0
let alpha = hexString.count == 8 ? CGFloat((rgbValue & 0xFF000000) >> 24) / 255.0 : 1.0

// Create and return UIColor
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}

var rgbValue: UInt64 = 0
Scanner(string: noHashString).scanHexInt64(&rgbValue)
// Try parsing color as named color
if let namedColor = UIColor(named: colorString) {
return namedColor
}

let r = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0
let g = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0
let b = CGFloat(rgbValue & 0x0000FF) / 255.0
// Try parsing color as RGB or RGBA
let rgbRegex = try! NSRegularExpression(pattern: #"rgba?\((\d{1,3}), (\d{1,3}), (\d{1,3})(, (\d(\.\d)?))?\)"#)
if let rgbMatch = rgbRegex.firstMatch(in: colorString, range: NSRange(colorString.startIndex..., in: colorString)) {
let red = Int(colorString[Range(rgbMatch.range(at: 1), in: colorString)!])!
let green = Int(colorString[Range(rgbMatch.range(at: 2), in: colorString)!])!
let blue = Int(colorString[Range(rgbMatch.range(at: 3), in: colorString)!])!
let alpha = rgbMatch.numberOfRanges > 5 ? Float(colorString[Range(rgbMatch.range(at: 5), in: colorString)!])! : 1.0

// If the color string starts with "rgb(", ignore the alpha component
if colorString.hasPrefix("rgb(") {
return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
} else {
return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: CGFloat(alpha))
}
}

return UIColor(red: r, green: g, blue: b, alpha: 1.0)
// Fallback to black color
return .black
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/Type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export interface ToastOptions {
* Defaults to `null`.
*/
icon?: Icon;

/**
* progress color for spinner preset
* @platform android
*/
progressColor?: string;
}

export interface AlertOptions {
Expand Down Expand Up @@ -87,4 +93,10 @@ export interface AlertOptions {
* custom icon
*/
icon?: Icon;

/**
* progress color for spinner preset
* @platform android
*/
progressColor?: string;
}

0 comments on commit 198a73c

Please sign in to comment.