1
1
package uk.akane.omni.logic
2
2
3
+ import android.animation.TimeInterpolator
4
+ import android.content.Context
5
+ import android.content.pm.PackageManager
6
+ import android.content.res.Configuration
7
+ import android.graphics.Color
8
+ import android.graphics.Interpolator
9
+ import android.hardware.SensorManager
10
+ import androidx.core.graphics.Insets
11
+ import android.os.Build
12
+ import android.os.Looper
13
+ import android.os.StrictMode
14
+ import android.view.Display
15
+ import android.view.View
16
+ import android.widget.TextView
17
+ import androidx.activity.ComponentActivity
18
+ import androidx.activity.SystemBarStyle
19
+ import androidx.activity.enableEdgeToEdge
20
+ import androidx.core.content.ContextCompat
21
+ import androidx.core.view.ViewCompat
22
+ import androidx.core.view.WindowInsetsCompat
23
+ import androidx.core.view.children
24
+ import com.google.android.material.appbar.AppBarLayout
25
+ import com.google.android.material.appbar.CollapsingToolbarLayout
26
+ import uk.akane.omni.BuildConfig
27
+
28
+ @Suppress(" NOTHING_TO_INLINE" )
29
+ inline fun Context.doIHavePermission (perm : String ) =
30
+ ContextCompat .checkSelfPermission(this , perm) == PackageManager .PERMISSION_GRANTED
31
+
32
+ val Context .isLocationPermissionGranted: Boolean
33
+ get() = doIHavePermission(android.Manifest .permission.ACCESS_COARSE_LOCATION )
34
+ || doIHavePermission(android.Manifest .permission.ACCESS_FINE_LOCATION )
35
+
36
+ fun ComponentActivity.enableEdgeToEdgeProperly () {
37
+ if ((resources.configuration.uiMode and Configuration .UI_MODE_NIGHT_MASK ) ==
38
+ Configuration .UI_MODE_NIGHT_YES
39
+ ) {
40
+ enableEdgeToEdge(navigationBarStyle = SystemBarStyle .dark(Color .TRANSPARENT ))
41
+ } else {
42
+ val darkScrim = Color .argb(0x80 , 0x1b , 0x1b , 0x1b )
43
+ enableEdgeToEdge(navigationBarStyle = SystemBarStyle .light(Color .TRANSPARENT , darkScrim))
44
+ }
45
+ }
46
+
47
+ fun View.fadOutAnimation (
48
+ duration : Long = 300,
49
+ visibility : Int = View .INVISIBLE ,
50
+ interpolator : TimeInterpolator ,
51
+ completion : (() -> Unit )? = null
52
+ ) {
53
+ animate()
54
+ .alpha(0f )
55
+ .setDuration(duration)
56
+ .setInterpolator(interpolator)
57
+ .withEndAction {
58
+ this .visibility = visibility
59
+ completion?.let {
60
+ it()
61
+ }
62
+ }
63
+ }
64
+
65
+ fun View.fadInAnimation (
66
+ duration : Long = 300,
67
+ completion : (() -> Unit )? = null,
68
+ interpolator : TimeInterpolator
69
+ ) {
70
+ alpha = 0f
71
+ visibility = View .VISIBLE
72
+ animate()
73
+ .alpha(1f )
74
+ .setDuration(duration)
75
+ .setInterpolator(interpolator)
76
+ .withEndAction {
77
+ completion?.let {
78
+ it()
79
+ }
80
+ }
81
+ }
82
+
83
+ fun TextView.setTextAnimation (
84
+ text : CharSequence? ,
85
+ duration : Long = 300,
86
+ completion : (() -> Unit )? = null,
87
+ skipAnimation : Boolean = false,
88
+ fadeInInterpolator : TimeInterpolator ,
89
+ fadeOutInterpolator : TimeInterpolator
90
+ ) {
91
+ if (skipAnimation) {
92
+ this .text = text
93
+ completion?.let { it() }
94
+ } else if (this .text != text) {
95
+ fadOutAnimation(duration, View .INVISIBLE , fadeOutInterpolator) {
96
+ this .text = text
97
+ fadInAnimation(duration, interpolator = fadeInInterpolator, completion = {
98
+ completion?.let {
99
+ it()
100
+ }
101
+ })
102
+ }
103
+ } else {
104
+ completion?.let { it() }
105
+ }
106
+ }
107
+
108
+ fun SensorManager.checkSensorAvailability (sensorType : Int ): Boolean {
109
+ return getDefaultSensor(sensorType) != null
110
+ }
111
+
112
+ // the whole point of this function is to do literally nothing at all (but without impacting
113
+ // performance) in release builds and ignore StrictMode violations in debug builds
114
+ inline fun <reified T > allowDiskAccessInStrictMode (doIt : () -> T ): T {
115
+ return if (BuildConfig .DEBUG ) {
116
+ if (Looper .getMainLooper() != Looper .myLooper()) throw IllegalStateException ()
117
+ val policy = StrictMode .allowThreadDiskReads()
118
+ try {
119
+ StrictMode .allowThreadDiskWrites()
120
+ doIt()
121
+ } finally {
122
+ StrictMode .setThreadPolicy(policy)
123
+ }
124
+ } else doIt()
125
+ }
126
+
127
+ fun View.enableEdgeToEdgePaddingListener (ime : Boolean = false, top : Boolean = false,
128
+ extra : ((Insets ) -> Unit )? = null) {
129
+ if (fitsSystemWindows) throw IllegalArgumentException (" must have fitsSystemWindows disabled" )
130
+ if (this is AppBarLayout ) {
131
+ if (ime) throw IllegalArgumentException (" AppBarLayout must have ime flag disabled" )
132
+ // AppBarLayout fitsSystemWindows does not handle left/right for a good reason, it has
133
+ // to be applied to children to look good; we rewrite fitsSystemWindows in a way mostly specific
134
+ // to Gramophone to support shortEdges displayCutout
135
+ val collapsingToolbarLayout = children.find { it is CollapsingToolbarLayout } as CollapsingToolbarLayout ?
136
+ collapsingToolbarLayout?.let {
137
+ // The CollapsingToolbarLayout mustn't consume insets, we handle padding here anyway
138
+ ViewCompat .setOnApplyWindowInsetsListener(it) { _, insets -> insets }
139
+ }
140
+ val expandedTitleMarginStart = collapsingToolbarLayout?.expandedTitleMarginStart
141
+ val expandedTitleMarginEnd = collapsingToolbarLayout?.expandedTitleMarginEnd
142
+ ViewCompat .setOnApplyWindowInsetsListener(this ) { v, insets ->
143
+ val cutoutAndBars = insets.getInsets(
144
+ WindowInsetsCompat .Type .systemBars()
145
+ or WindowInsetsCompat .Type .displayCutout()
146
+ )
147
+ (v as AppBarLayout ).children.forEach {
148
+ if (it is CollapsingToolbarLayout ) {
149
+ val es = expandedTitleMarginStart!! + if (it.layoutDirection
150
+ == View .LAYOUT_DIRECTION_LTR ) cutoutAndBars.left else cutoutAndBars.right
151
+ if (es != it.expandedTitleMarginStart) it.expandedTitleMarginStart = es
152
+ val ee = expandedTitleMarginEnd!! + if (it.layoutDirection
153
+ == View .LAYOUT_DIRECTION_RTL ) cutoutAndBars.left else cutoutAndBars.right
154
+ if (ee != it.expandedTitleMarginEnd) it.expandedTitleMarginEnd = ee
155
+ }
156
+ it.setPadding(cutoutAndBars.left, 0 , cutoutAndBars.right, 0 )
157
+ }
158
+ v.setPadding(0 , cutoutAndBars.top, 0 , 0 )
159
+ val i = insets.getInsetsIgnoringVisibility(WindowInsetsCompat .Type .systemBars()
160
+ or WindowInsetsCompat .Type .displayCutout())
161
+ extra?.invoke(cutoutAndBars)
162
+ return @setOnApplyWindowInsetsListener WindowInsetsCompat .Builder (insets)
163
+ .setInsets(WindowInsetsCompat .Type .systemBars()
164
+ or WindowInsetsCompat .Type .displayCutout(), Insets .of(cutoutAndBars.left, 0 , cutoutAndBars.right, cutoutAndBars.bottom))
165
+ .setInsetsIgnoringVisibility(WindowInsetsCompat .Type .systemBars()
166
+ or WindowInsetsCompat .Type .displayCutout(), Insets .of(i.left, 0 , i.right, i.bottom))
167
+ .build()
168
+ }
169
+ } else {
170
+ val pl = paddingLeft
171
+ val pt = paddingTop
172
+ val pr = paddingRight
173
+ val pb = paddingBottom
174
+ ViewCompat .setOnApplyWindowInsetsListener(this ) { v, insets ->
175
+ val mask = WindowInsetsCompat .Type .systemBars() or
176
+ WindowInsetsCompat .Type .displayCutout() or
177
+ if (ime) WindowInsetsCompat .Type .ime() else 0
178
+ val i = insets.getInsets(mask)
179
+ v.setPadding(pl + i.left, pt + (if (top) i.top else 0 ), pr + i.right,
180
+ pb + i.bottom)
181
+ extra?.invoke(i)
182
+ return @setOnApplyWindowInsetsListener WindowInsetsCompat .Builder (insets)
183
+ .setInsets(mask, Insets .NONE )
184
+ .setInsetsIgnoringVisibility(mask, Insets .NONE )
185
+ .build()
186
+ }
187
+ }
188
+ }
189
+
190
+ @Suppress(" NOTHING_TO_INLINE" )
191
+ inline fun Int.dpToPx (context : Context ): Int =
192
+ (this .toFloat() * context.resources.displayMetrics.density).toInt()
0 commit comments