Merge pull request #1 from Xe/better-widget

Better widget
This commit is contained in:
Cadey Ratio 2021-07-05 20:51:15 -04:00 committed by GitHub
commit ca74907444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 323 additions and 78 deletions

View File

@ -17,6 +17,7 @@
import groovy.xml.MarkupBuilder import groovy.xml.MarkupBuilder
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
def twaManifest = [ def twaManifest = [
applicationId: 'website.christine.xesite', applicationId: 'website.christine.xesite',
@ -153,6 +154,9 @@ android {
lintOptions { lintOptions {
checkReleaseBuilds false checkReleaseBuilds false
} }
buildFeatures {
viewBinding true
}
} }
task generateShorcutsFile { task generateShorcutsFile {
@ -202,5 +206,7 @@ dependencies {
implementation 'com.google.androidbrowserhelper:locationdelegation:1.0.0' implementation 'com.google.androidbrowserhelper:locationdelegation:1.0.0'
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.2.0' implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.android.volley:volley:1.2.0'
implementation 'com.google.code.gson:gson:2.8.7'
} }

View File

@ -1,48 +1,33 @@
<!-- <?xml version="1.0" encoding="utf-8"?>
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The "package" attribute is rewritten by the Gradle build with the value of applicationId.
It is still required here, as it is used to derive paths, for instance when referring
to an Activity by ".MyActivity" instead of the full name. If more Activities are added to the
application, the package attribute will need to reflect the correct path in order to use
the abbreviated format. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="website.christine.xesite"> package="website.christine.xesite">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name="Application" android:name=".Application"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/appName" android:label="@string/appName"
android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity" android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"> android:theme="@android:style/Theme.Translucent.NoTitleBar">
<receiver android:name=".NewPostWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/new_post_widget_info" />
</receiver>
<meta-data <meta-data
android:name="asset_statements" android:name="asset_statements"
android:resource="@string/assetStatements" /> android:resource="@string/assetStatements" />
<meta-data <meta-data
android:name="web_manifest_url" android:name="web_manifest_url"
android:value="@string/webManifestUrl" /> android:value="@string/webManifestUrl" />
<meta-data <meta-data
android:name="twa_generator" android:name="twa_generator"
android:value="@string/generatorApp" /> android:value="@string/generatorApp" />
@ -52,69 +37,56 @@
android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL" android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"
android:value="@string/launchUrl" /> android:value="@string/launchUrl" />
</activity> </activity>
<activity <activity
android:name="LauncherActivity" android:name=".LauncherActivity"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
android:label="@string/launcherName" android:label="@string/launcherName"
android:screenOrientation="unspecified"> android:screenOrientation="unspecified">
<meta-data <meta-data
android:name="android.support.customtabs.trusted.DEFAULT_URL" android:name="android.support.customtabs.trusted.DEFAULT_URL"
android:value="@string/launchUrl" /> android:value="@string/launchUrl" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR" android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR"
android:resource="@color/colorPrimary" /> android:resource="@color/colorPrimary" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR" android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR"
android:resource="@color/navigationColor" /> android:resource="@color/navigationColor" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR_DARK" android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR_DARK"
android:resource="@color/navigationColorDark" /> android:resource="@color/navigationColorDark" />
<meta-data <meta-data
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR" android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR"
android:resource="@color/navigationDividerColor" /> android:resource="@color/navigationDividerColor" />
<meta-data <meta-data
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR_DARK" android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR_DARK"
android:resource="@color/navigationDividerColorDark" /> android:resource="@color/navigationDividerColorDark" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.SPLASH_IMAGE_DRAWABLE" android:name="android.support.customtabs.trusted.SPLASH_IMAGE_DRAWABLE"
android:resource="@drawable/splash" /> android:resource="@drawable/splash" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.SPLASH_SCREEN_BACKGROUND_COLOR" android:name="android.support.customtabs.trusted.SPLASH_SCREEN_BACKGROUND_COLOR"
android:resource="@color/backgroundColor" /> android:resource="@color/backgroundColor" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.SPLASH_SCREEN_FADE_OUT_DURATION" android:name="android.support.customtabs.trusted.SPLASH_SCREEN_FADE_OUT_DURATION"
android:value="@integer/splashScreenFadeOutDuration" /> android:value="@integer/splashScreenFadeOutDuration" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY" android:name="android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY"
android:value="@string/providerAuthority" /> android:value="@string/providerAuthority" />
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.FALLBACK_STRATEGY" android:name="android.support.customtabs.trusted.FALLBACK_STRATEGY"
android:value="@string/fallbackType" /> android:value="@string/fallbackType" />
<meta-data <meta-data
android:name="android.support.customtabs.trusted.SCREEN_ORIENTATION" android:name="android.support.customtabs.trusted.SCREEN_ORIENTATION"
android:value="@string/orientation" /> android:value="@string/orientation" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -126,9 +98,7 @@
android:scheme="https" /> android:scheme="https" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.google.androidbrowserhelper.trusted.FocusActivity" /> <activity android:name="com.google.androidbrowserhelper.trusted.FocusActivity" />
<activity <activity
android:name="com.google.androidbrowserhelper.trusted.WebViewFallbackActivity" android:name="com.google.androidbrowserhelper.trusted.WebViewFallbackActivity"
android:configChanges="orientation|screenSize" /> android:configChanges="orientation|screenSize" />
@ -147,14 +117,14 @@
android:name=".DelegationService" android:name=".DelegationService"
android:enabled="@bool/enableNotification" android:enabled="@bool/enableNotification"
android:exported="@bool/enableNotification"> android:exported="@bool/enableNotification">
<intent-filter> <intent-filter>
<action android:name="android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE" /> <action android:name="android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</service> </service>
<activity android:name="com.google.androidbrowserhelper.locationdelegation.PermissionRequestActivity" /> <activity android:name="com.google.androidbrowserhelper.locationdelegation.PermissionRequestActivity" />
</application> </application>
</manifest>
</manifest>

View File

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package website.christine.xesite; package website.christine.xesite
import android.app.Application
public class Application extends android.app.Application { class Application : Application() {
@Override override fun onCreate() {
public void onCreate() { super.onCreate()
super.onCreate();
} }
} }

View File

@ -1,14 +0,0 @@
package website.christine.xesite;
import com.google.androidbrowserhelper.locationdelegation.LocationDelegationExtraCommandHandler;
public class DelegationService extends
com.google.androidbrowserhelper.trusted.DelegationService {
@Override
public void onCreate() {
super.onCreate();
registerExtraCommandHandler(new LocationDelegationExtraCommandHandler());
}
}

View File

@ -0,0 +1,11 @@
package website.christine.xesite
import com.google.androidbrowserhelper.locationdelegation.LocationDelegationExtraCommandHandler
import com.google.androidbrowserhelper.trusted.DelegationService
class DelegationService : DelegationService() {
override fun onCreate() {
super.onCreate()
registerExtraCommandHandler(LocationDelegationExtraCommandHandler())
}
}

View File

@ -0,0 +1,49 @@
package website.christine.xesite
import com.android.volley.NetworkResponse
import com.android.volley.ParseError
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.HttpHeaderParser
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
/**
* Make a GET request and return a parsed object from JSON.
*
* @param url URL of the request to make
* @param clazz Relevant class object, for Gson's reflection
* @param headers Map of request headers
*
* From here: https://developer.android.com/training/volley/request-custom
*/
class GsonGetRequest<T>(
url: String,
private val clazz: Class<T>,
private val headers: MutableMap<String, String>?,
private val listener: Response.Listener<T>,
errorListener: Response.ErrorListener
) : Request<T>(Method.GET, url, errorListener) {
private val gson = Gson()
override fun getHeaders(): MutableMap<String, String> = headers ?: super.getHeaders()
override fun deliverResponse(response: T) = listener.onResponse(response)
override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
return try {
val json = String(
response?.data ?: ByteArray(0),
Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response))
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
} catch (e: JsonSyntaxException) {
Response.error(ParseError(e))
}
}
}

View File

@ -13,18 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package website.christine.xesite; package website.christine.xesite
import android.net.Uri; import android.net.Uri
import com.google.androidbrowserhelper.trusted.LauncherActivity
public class LauncherActivity class LauncherActivity : LauncherActivity() {
extends com.google.androidbrowserhelper.trusted.LauncherActivity { override fun getLaunchingUrl(): Uri {
@Override
protected Uri getLaunchingUrl() {
// Get the original launch Url. // Get the original launch Url.
Uri uri = super.getLaunchingUrl(); return super.getLaunchingUrl()
return uri;
} }
} }

View File

@ -0,0 +1,7 @@
package website.christine.xesite
class NewPost (
val title: String,
val summary: String,
val link: String,
)

View File

@ -0,0 +1,105 @@
package website.christine.xesite
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.util.Log
import android.widget.RemoteViews
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.BasicNetwork
import com.android.volley.toolbox.DiskBasedCache
import com.android.volley.toolbox.HurlStack
/**
* Implementation of App Widget functionality.
*/
class NewPostWidget : AppWidgetProvider() {
private lateinit var requestQueue: RequestQueue
private fun userAgent(ctx: Context): String {
val pkgInfo = ctx.getPackageManager().getPackageInfo(ctx.packageName, 0)
return ctx.packageName.plus("/").plus(pkgInfo.versionName)
}
override fun onUpdate(
ctx: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val url = "https://christine.website/.within/website.within.xesite/new_post"
val headers: MutableMap<String, String> = mutableMapOf()
headers.put("User-Agent", this.userAgent(ctx));
val jor: GsonGetRequest<NewPost> = GsonGetRequest(
url,
NewPost::class.java,
headers,
Response.Listener<NewPost> { response ->
Log.println(Log.INFO, "new_post", response.toString())
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
this.updateAppWidget(ctx, appWidgetManager, appWidgetId, response)
}
}, Response.ErrorListener { error ->
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
this.updateAppWidget(
ctx,
appWidgetManager,
appWidgetId,
NewPost("Error", error.toString(), "")
)
}
})
if (!this::requestQueue.isInitialized) {
this.makeQueue(ctx)
}
this.requestQueue.add(jor)
}
private fun makeQueue(ctx: Context) {
// Instantiate the cache
val cache = DiskBasedCache(ctx.cacheDir, 1024 * 1024) // 1MB cap
// Set up the network to use HttpURLConnection as the HTTP client.
val network = BasicNetwork(HurlStack())
this.requestQueue = RequestQueue(cache, network).apply {
start()
}
}
override fun onEnabled(ctx: Context) {
this.makeQueue(ctx)
}
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
private fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
body: NewPost
) {
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.new_post_widget)
views.setTextViewText(
R.id.article_title,
body.title
)
views.setTextViewText(
R.id.article_preview,
body.summary
)
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,59 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundColor"
android:focusable="auto"
android:padding="@dimen/widget_margin"
android:theme="@style/ThemeOverlay.Xesite_android.AppWidgetContainer">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="109dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="92dp"
android:layout_height="75dp"
android:src="@drawable/splash" />
<TextView
android:id="@+id/article_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="8dp"
android:background="@color/backgroundColor"
android:contentDescription="@string/appwidget_title"
android:text="@string/appwidget_title"
android:textColor="#3C3836"
android:textSize="24sp"
android:textStyle="bold|italic" />
</LinearLayout>
<TextView
android:id="@+id/article_preview"
android:layout_width="match_parent"
android:layout_height="117dp"
android:layout_marginStart="8dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@color/backgroundColor"
android:contentDescription="@string/appwidget_body_preview"
android:text="@string/appwidget_body_preview"
android:textColor="#282828"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,7 @@
<resources>
<style name="ThemeOverlay.Xesite_android.AppWidgetContainer" parent="">
<item name="appWidgetBackgroundColor">@color/light_blue_900</item>
<item name="appWidgetTextColor">@color/light_blue_200</item>
</style>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<declare-styleable name="AppWidgetAttrs">
<attr name="appWidgetBackgroundColor" format="color" />
<attr name="appWidgetTextColor" format="color" />
</declare-styleable>
</resources>

View File

@ -15,4 +15,8 @@
--> -->
<resources> <resources>
<color name="shortcut_background">#F5F5F5</color> <color name="shortcut_background">#F5F5F5</color>
<color name="light_blue_50">#FFE1F5FE</color>
<color name="light_blue_200">#FF81D4FA</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
</resources> </resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Refer to App Widget Documentation for margin information
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
-->
<dimen name="widget_margin">0dp</dimen>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="appwidget_title">EXAMPLE TITLE</string>
<string name="appwidget_body_preview">These are some words that let you see what an article preview would look like.</string>
<string name="add_widget">Add widget</string>
<string name="new_post_channel_name">New Posts</string>
<string name="new_post_channel_desc">Brand new posts on christine.website</string>
</resources>

View File

@ -0,0 +1,7 @@
<resources>
<style name="ThemeOverlay.Xesite_android.AppWidgetContainer" parent="">
<item name="appWidgetBackgroundColor">@color/light_blue_600</item>
<item name="appWidgetTextColor">@color/light_blue_50</item>
</style>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/new_post_widget"
android:initialLayout="@layout/new_post_widget"
android:minWidth="250dp"
android:minHeight="80dp"
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="28800000"
android:widgetCategory="home_screen"></appwidget-provider>

View File

@ -18,12 +18,16 @@
buildscript { buildscript {
ext {
kotlin_version = '1.5.0'
}
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.1' classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files