diff --git a/app/build.gradle b/app/build.gradle index c28c3cd..7f3c0a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,7 @@ import groovy.xml.MarkupBuilder apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' def twaManifest = [ applicationId: 'website.christine.xesite', @@ -153,6 +154,9 @@ android { lintOptions { checkReleaseBuilds false } + buildFeatures { + viewBinding true + } } task generateShorcutsFile { @@ -202,5 +206,7 @@ dependencies { implementation 'com.google.androidbrowserhelper:locationdelegation:1.0.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' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11d1cd4..1f68122 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,48 +1,33 @@ - - - + + + + + + + + - - @@ -52,69 +37,56 @@ android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL" android:value="@string/launchUrl" /> - - - - - - - - - - - - - + - @@ -126,9 +98,7 @@ android:scheme="https" /> - - @@ -147,14 +117,14 @@ android:name=".DelegationService" android:enabled="@bool/enableNotification" android:exported="@bool/enableNotification"> - + - - + + \ No newline at end of file diff --git a/app/src/main/java/website/christine/xesite/Application.java b/app/src/main/java/website/christine/xesite/Application.kt similarity index 78% rename from app/src/main/java/website/christine/xesite/Application.java rename to app/src/main/java/website/christine/xesite/Application.kt index aa89f6a..c818d01 100644 --- a/app/src/main/java/website/christine/xesite/Application.java +++ b/app/src/main/java/website/christine/xesite/Application.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package website.christine.xesite; +package website.christine.xesite +import android.app.Application -public class Application extends android.app.Application { - @Override - public void onCreate() { - super.onCreate(); +class Application : Application() { + override fun onCreate() { + super.onCreate() } -} +} \ No newline at end of file diff --git a/app/src/main/java/website/christine/xesite/DelegationService.java b/app/src/main/java/website/christine/xesite/DelegationService.java deleted file mode 100644 index 252e8cb..0000000 --- a/app/src/main/java/website/christine/xesite/DelegationService.java +++ /dev/null @@ -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()); - } -} - diff --git a/app/src/main/java/website/christine/xesite/DelegationService.kt b/app/src/main/java/website/christine/xesite/DelegationService.kt new file mode 100644 index 0000000..ad467cd --- /dev/null +++ b/app/src/main/java/website/christine/xesite/DelegationService.kt @@ -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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/website/christine/xesite/GsonGetRequest.kt b/app/src/main/java/website/christine/xesite/GsonGetRequest.kt new file mode 100644 index 0000000..f51f36e --- /dev/null +++ b/app/src/main/java/website/christine/xesite/GsonGetRequest.kt @@ -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( + url: String, + private val clazz: Class, + private val headers: MutableMap?, + private val listener: Response.Listener, + errorListener: Response.ErrorListener +) : Request(Method.GET, url, errorListener) { + private val gson = Gson() + + override fun getHeaders(): MutableMap = headers ?: super.getHeaders() + + override fun deliverResponse(response: T) = listener.onResponse(response) + + override fun parseNetworkResponse(response: NetworkResponse?): Response { + 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)) + } + } +} diff --git a/app/src/main/java/website/christine/xesite/LauncherActivity.java b/app/src/main/java/website/christine/xesite/LauncherActivity.kt similarity index 69% rename from app/src/main/java/website/christine/xesite/LauncherActivity.java rename to app/src/main/java/website/christine/xesite/LauncherActivity.kt index ddf4d8f..ebf6c4e 100644 --- a/app/src/main/java/website/christine/xesite/LauncherActivity.java +++ b/app/src/main/java/website/christine/xesite/LauncherActivity.kt @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * 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 - extends com.google.androidbrowserhelper.trusted.LauncherActivity { - - @Override - protected Uri getLaunchingUrl() { +class LauncherActivity : LauncherActivity() { + override fun getLaunchingUrl(): Uri { // Get the original launch Url. - Uri uri = super.getLaunchingUrl(); - - return uri; + return super.getLaunchingUrl() } -} +} \ No newline at end of file diff --git a/app/src/main/java/website/christine/xesite/NewPost.kt b/app/src/main/java/website/christine/xesite/NewPost.kt new file mode 100644 index 0000000..fad5a6d --- /dev/null +++ b/app/src/main/java/website/christine/xesite/NewPost.kt @@ -0,0 +1,7 @@ +package website.christine.xesite + +class NewPost ( + val title: String, + val summary: String, + val link: String, +) \ No newline at end of file diff --git a/app/src/main/java/website/christine/xesite/NewPostWidget.kt b/app/src/main/java/website/christine/xesite/NewPostWidget.kt new file mode 100644 index 0000000..9b0692f --- /dev/null +++ b/app/src/main/java/website/christine/xesite/NewPostWidget.kt @@ -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 = mutableMapOf() + headers.put("User-Agent", this.userAgent(ctx)); + + val jor: GsonGetRequest = GsonGetRequest( + url, + NewPost::class.java, + headers, + Response.Listener { 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) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-nodpi/example_appwidget_preview.png b/app/src/main/res/drawable-nodpi/example_appwidget_preview.png new file mode 100644 index 0000000..5861a0e Binary files /dev/null and b/app/src/main/res/drawable-nodpi/example_appwidget_preview.png differ diff --git a/app/src/main/res/layout/new_post_widget.xml b/app/src/main/res/layout/new_post_widget.xml new file mode 100644 index 0000000..0a7a166 --- /dev/null +++ b/app/src/main/res/layout/new_post_widget.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..cde4114 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..97531a2 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e66222d..58b6495 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,4 +15,8 @@ --> #F5F5F5 + #FFE1F5FE + #FF81D4FA + #FF039BE5 + #FF01579B diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..4db8c59 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + + + + 0dp + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..9f580c4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + EXAMPLE TITLE + These are some words that let you see what an article preview would look like. + Add widget + New Posts + Brand new posts on christine.website + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..911b6b4 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/new_post_widget_info.xml b/app/src/main/res/xml/new_post_widget_info.xml new file mode 100644 index 0000000..b365135 --- /dev/null +++ b/app/src/main/res/xml/new_post_widget_info.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7a99ebf..0d68f0a 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,16 @@ buildscript { + ext { + kotlin_version = '1.5.0' + } repositories { google() jcenter() } dependencies { 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 // in the individual module build.gradle files