Skip to content

Commit

Permalink
feat: add sample widget
Browse files Browse the repository at this point in the history
  • Loading branch information
callasio committed Sep 12, 2024
1 parent 7e18694 commit 1e694b6
Show file tree
Hide file tree
Showing 21 changed files with 401 additions and 33 deletions.
3 changes: 3 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ android {
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
}

flutter {
Expand Down
76 changes: 51 additions & 25 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,49 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that

<!--
io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission
android:name="android.permission.INTERNET"/>
FlutterApplication and put your custom class here.
-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:name="${applicationName}"
android:label="OTL"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:fullBackupContent="true">
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="OTL">
<receiver
android:name=".OTLTimelineWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/o_t_l_timeline_widget_info" />
</receiver>

<activity
android:name=".OTLTimelineWidgetConfigureActivity"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:exported="true">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.kuku.channel_talk_flutter.PushInterceptService"
android:exported="true"
>

<service
android:name="com.kuku.channel_talk_flutter.PushInterceptService"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<!--
Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
-->
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
android:value="2" />
</application>

</manifest>
52 changes: 52 additions & 0 deletions android/app/src/main/java/org/sparcs/otlplus/OTLTimelineWidget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.sparcs.otlplus

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

/**
* Implementation of App Widget functionality.
* App Widget Configuration implemented in [OTLTimelineWidgetConfigureActivity]
*/
class OTLTimelineWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}

override fun onDeleted(context: Context, appWidgetIds: IntArray) {
// When the user deletes the widget, delete the preference associated with it.
for (appWidgetId in appWidgetIds) {
deleteTitlePref(context, appWidgetId)
}
}

override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}

override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}

internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val widgetText = loadTitlePref(context, appWidgetId)
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.o_t_l_timeline_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)

// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.sparcs.otlplus

import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.EditText
import org.sparcs.otlplus.databinding.OTLTimelineWidgetConfigureBinding

/**
* The configuration screen for the [OTLTimelineWidget] AppWidget.
*/
class OTLTimelineWidgetConfigureActivity : Activity() {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
private lateinit var appWidgetText: EditText
private var onClickListener = View.OnClickListener {
val context = this@OTLTimelineWidgetConfigureActivity

// When the button is clicked, store the string locally
val widgetText = appWidgetText.text.toString()
saveTitlePref(context, appWidgetId, widgetText)

// It is the responsibility of the configuration activity to update the app widget
val appWidgetManager = AppWidgetManager.getInstance(context)
updateAppWidget(context, appWidgetManager, appWidgetId)

// Make sure we pass back the original appWidgetId
val resultValue = Intent()
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
setResult(RESULT_OK, resultValue)
finish()
}
private lateinit var binding: OTLTimelineWidgetConfigureBinding

public override fun onCreate(icicle: Bundle?) {
super.onCreate(icicle)

// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if the user presses the back button.
setResult(RESULT_CANCELED)

binding = OTLTimelineWidgetConfigureBinding.inflate(layoutInflater)
setContentView(binding.root)

appWidgetText = binding.appwidgetText as EditText
binding.addButton.setOnClickListener(onClickListener)

// Find the widget id from the intent.
val intent = intent
val extras = intent.extras
if (extras != null) {
appWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID
)
}

// If this activity was started with an intent without an app widget ID, finish with an error.
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish()
return
}

appWidgetText.setText(loadTitlePref(this@OTLTimelineWidgetConfigureActivity, appWidgetId))
}

}

private const val PREFS_NAME = "org.sparcs.otlplus.OTLTimelineWidget"
private const val PREF_PREFIX_KEY = "appwidget_"

// Write the prefix to the SharedPreferences object for this widget
internal fun saveTitlePref(context: Context, appWidgetId: Int, text: String) {
val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit()
prefs.putString(PREF_PREFIX_KEY + appWidgetId, text)
prefs.apply()
}

// Read the prefix from the SharedPreferences object for this widget.
// If there is no preference saved, get the default from a resource
internal fun loadTitlePref(context: Context, appWidgetId: Int): String {
val prefs = context.getSharedPreferences(PREFS_NAME, 0)
val titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null)
return titleValue ?: context.getString(R.string.appwidget_text)
}

internal fun deleteTitlePref(context: Context, appWidgetId: Int) {
val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit()
prefs.remove(PREF_PREFIX_KEY + appWidgetId)
prefs.apply()
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions android/app/src/main/res/drawable-v21/app_widget_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!--
Background for widgets to make the rounded corners based on the
appWidgetRadius attribute value
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<corners android:radius="?attr/appWidgetRadius" />
<solid android:color="?android:attr/colorBackground" />
</shape>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!--
Background for views inside widgets to make the rounded corners based on the
appWidgetInnerRadius attribute value
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<corners android:radius="?attr/appWidgetInnerRadius" />
<solid android:color="?android:attr/colorAccent" />
</shape>
19 changes: 19 additions & 0 deletions android/app/src/main/res/layout/o_t_l_timeline_widget.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/Theme.Android.AppWidgetContainer">

<TextView
android:id="@+id/appwidget_text"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:contentDescription="@string/appwidget_text"
android:text="@string/appwidget_text"
android:textSize="24sp"
android:textStyle="bold|italic" />
</RelativeLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:labelFor="@id/appwidget_text"
android:text="@string/configure" />

<EditText
android:id="@+id/appwidget_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />

<Button
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/add_widget" />

</LinearLayout>
10 changes: 10 additions & 0 deletions android/app/src/main/res/values-night-v31/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
-->
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>
</resources>
14 changes: 14 additions & 0 deletions android/app/src/main/res/values-v21/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<resources>

<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_background</item>
</style>

<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_inner_view_background</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
</resources>
14 changes: 14 additions & 0 deletions android/app/src/main/res/values-v31/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,18 @@
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>

<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_background</item>
<item name="android:clipToOutline">true</item>
</style>

<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_inner_view_background</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:clipToOutline">true</item>
</style>
</resources>
11 changes: 11 additions & 0 deletions android/app/src/main/res/values-v31/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Having themes.xml for v31 variant because @android:dimen/system_app_widget_background_radius
and @android:dimen/system_app_widget_internal_padding requires API level 31
-->
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>
</resources>
Loading

0 comments on commit 1e694b6

Please sign in to comment.