Skip to content

Commit 1999a95

Browse files
committed
Add widget support
Signed-off-by: Kyle Corry <[email protected]>
1 parent d510a63 commit 1999a95

File tree

11 files changed

+222
-40
lines changed

11 files changed

+222
-40
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ local.properties
1919
.kotlin/
2020
.idea/other.xml
2121
.idea/deploymentTargetSelector.xml
22+
**/build/

.idea/compiler.xml

Lines changed: 1 addition & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/src/main/java/com/kylecorry/andromeda/core/system/Resources.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.R
44
import android.content.Context
55
import android.content.res.Configuration
66
import android.graphics.drawable.Drawable
7-
import android.os.LocaleList
87
import android.text.format.DateFormat
98
import android.util.TypedValue
109
import android.view.MenuItem
@@ -14,6 +13,7 @@ import androidx.annotation.ColorInt
1413
import androidx.annotation.ColorRes
1514
import androidx.annotation.DrawableRes
1615
import androidx.annotation.MenuRes
16+
import androidx.annotation.StyleRes
1717
import androidx.core.content.ContextCompat
1818
import androidx.core.content.res.ResourcesCompat
1919
import androidx.core.os.ConfigurationCompat
@@ -114,4 +114,8 @@ object Resources {
114114
fun getLocalizedResources(context: Context, locale: Locale): android.content.res.Resources {
115115
return getLocalizedContext(context, locale).resources
116116
}
117+
118+
fun reloadTheme(context: Context, @StyleRes themeResId: Int) {
119+
context.theme.applyStyle(themeResId, true)
120+
}
117121
}

core/src/main/java/com/kylecorry/andromeda/core/ui/Views.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.kylecorry.andromeda.core.ui
22

33
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.graphics.Canvas
46
import android.view.View
57
import android.view.ViewGroup
68
import android.widget.LinearLayout
79
import android.widget.ScrollView
810
import android.widget.TextView
11+
import androidx.core.view.drawToBitmap
912
import androidx.core.view.setPadding
1013

1114
object Views {
@@ -51,11 +54,41 @@ object Views {
5154
): View {
5255
return TextView(context).apply {
5356
layoutParams = ViewGroup.LayoutParams(width, height)
54-
if (id != null){
57+
if (id != null) {
5558
this.id = id
5659
}
5760
this.text = text
5861
}
5962
}
6063

64+
fun renderViewAsBitmap(view: View): Bitmap {
65+
view.measure(
66+
View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY),
67+
View.MeasureSpec.makeMeasureSpec(view.height, View.MeasureSpec.EXACTLY)
68+
)
69+
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
70+
return view.drawToBitmap()
71+
}
72+
73+
fun renderViewAsBitmap(view: View, width: Int, height: Int): Bitmap {
74+
view.layoutParams = ViewGroup.LayoutParams(
75+
width,
76+
height
77+
)
78+
view.measure(
79+
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
80+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
81+
)
82+
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
83+
return view.drawToBitmap()
84+
}
85+
86+
fun renderViewToCanvas(view: View, canvas: Canvas) {
87+
view.measure(
88+
View.MeasureSpec.makeMeasureSpec(canvas.width, View.MeasureSpec.EXACTLY),
89+
View.MeasureSpec.makeMeasureSpec(canvas.height, View.MeasureSpec.EXACTLY)
90+
)
91+
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
92+
view.draw(canvas)
93+
}
6194
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ include(":exceptions")
5757
include(":print")
5858
include(":tensorflow")
5959
include(":views")
60+
include(":widgets")

widgets/build.gradle

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
apply plugin: 'com.android.library'
2+
apply plugin: 'maven-publish'
3+
apply plugin: 'kotlin-android'
4+
5+
group = "$groupId"
6+
version = "$versionName"
7+
8+
android {
9+
compileSdkVersion = compileVersion
10+
namespace = "com.kylecorry.andromeda.widgets"
11+
12+
defaultConfig {
13+
minSdkVersion 23
14+
targetSdkVersion targetVersion
15+
16+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17+
}
18+
19+
packagingOptions {
20+
exclude 'META-INF/LICENSE.md'
21+
exclude 'META-INF/LICENSE-notice.md'
22+
}
23+
buildTypes {
24+
release {
25+
minifyEnabled false
26+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27+
}
28+
}
29+
compileOptions {
30+
coreLibraryDesugaringEnabled true
31+
sourceCompatibility JavaVersion.VERSION_11
32+
targetCompatibility JavaVersion.VERSION_11
33+
}
34+
kotlinOptions {
35+
jvmTarget = JavaVersion.VERSION_11.toString()
36+
}
37+
publishing {
38+
singleVariant('release') {
39+
withSourcesJar()
40+
}
41+
}
42+
}
43+
44+
afterEvaluate {
45+
publishing {
46+
publications {
47+
release(MavenPublication) {
48+
from components.release
49+
groupId = group
50+
artifactId = 'widgets'
51+
version = version
52+
}
53+
}
54+
}
55+
}
56+
57+
dependencies {
58+
implementation project(':core')
59+
implementation libs.androidx.core.ktx
60+
coreLibraryDesugaring libs.desugar
61+
testImplementation libs.junit
62+
androidTestImplementation libs.androidx.junit
63+
androidTestImplementation libs.androidx.espresso.core
64+
}

widgets/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest>
3+
4+
</manifest>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.kylecorry.andromeda.widgets
2+
3+
import android.appwidget.AppWidgetManager
4+
import android.appwidget.AppWidgetProvider
5+
import android.content.Context
6+
import android.widget.RemoteViews
7+
import com.kylecorry.andromeda.core.coroutines.onMain
8+
import com.kylecorry.andromeda.core.system.Resources
9+
import kotlinx.coroutines.CoroutineDispatcher
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.launch
13+
14+
abstract class AndromedaCoroutineWidget(
15+
private val themeToReload: Int? = null,
16+
private val dispatcher: CoroutineDispatcher = Dispatchers.Default
17+
) : AppWidgetProvider() {
18+
override fun onUpdate(
19+
context: Context,
20+
appWidgetManager: AppWidgetManager,
21+
appWidgetIds: IntArray
22+
) {
23+
if (themeToReload != null) {
24+
Resources.reloadTheme(context, themeToReload)
25+
}
26+
27+
val pendingResult = goAsync()
28+
try {
29+
CoroutineScope(dispatcher).launch {
30+
try {
31+
val views = getUpdatedRemoteViews(context, appWidgetManager)
32+
onMain {
33+
for (appWidgetId in appWidgetIds) {
34+
appWidgetManager.updateAppWidget(appWidgetId, views)
35+
}
36+
}
37+
} finally {
38+
pendingResult.finish()
39+
}
40+
}
41+
} catch (e: Exception) {
42+
pendingResult.finish()
43+
}
44+
}
45+
46+
/**
47+
* Get the updated remote views for the widget. This will be passed into appWidgetManager.updateAppWidget for all instances of the widget.
48+
* @param context The context
49+
* @param appWidgetManager The app widget manager
50+
* @return The updated remote views or null if the widget should not be updated
51+
*/
52+
protected abstract suspend fun getUpdatedRemoteViews(
53+
context: Context,
54+
appWidgetManager: AppWidgetManager
55+
): RemoteViews
56+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.kylecorry.andromeda.widgets
2+
3+
import android.appwidget.AppWidgetManager
4+
import android.appwidget.AppWidgetProvider
5+
import android.content.Context
6+
import android.widget.RemoteViews
7+
import com.kylecorry.andromeda.core.system.Resources
8+
9+
abstract class AndromedaWidget(private val themeToReload: Int? = null) : AppWidgetProvider() {
10+
override fun onUpdate(
11+
context: Context,
12+
appWidgetManager: AppWidgetManager,
13+
appWidgetIds: IntArray
14+
) {
15+
if (themeToReload != null) {
16+
Resources.reloadTheme(context, themeToReload)
17+
}
18+
19+
val views = getUpdatedRemoteViews(context, appWidgetManager)
20+
if (views != null) {
21+
for (appWidgetId in appWidgetIds) {
22+
appWidgetManager.updateAppWidget(appWidgetId, views)
23+
}
24+
}
25+
}
26+
27+
/**
28+
* Get the updated remote views for the widget. This will be passed into appWidgetManager.updateAppWidget for all instances of the widget.
29+
* @param context The context
30+
* @param appWidgetManager The app widget manager
31+
* @return The updated remote views or null if the widget should not be updated
32+
*/
33+
protected abstract fun getUpdatedRemoteViews(
34+
context: Context,
35+
appWidgetManager: AppWidgetManager
36+
): RemoteViews?
37+
}

0 commit comments

Comments
 (0)