Skip to content

Commit

Permalink
Added 6-day KNMI forecast (#44)
Browse files Browse the repository at this point in the history
#44

---------

Co-authored-by: Leonard <[email protected]>
  • Loading branch information
Lee245 and Leonard authored Feb 27, 2024
1 parent 113a295 commit ead8b06
Show file tree
Hide file tree
Showing 18 changed files with 388 additions and 61 deletions.
10 changes: 10 additions & 0 deletions .idea/migrations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdk 30
compileSdk 34
defaultConfig {
applicationId "foss.cnugteren.nlweer"
minSdkVersion 21
targetSdkVersion 30
targetSdkVersion 34
versionCode 11
versionName "1.9.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/AppTheme.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/foss/cnugteren/nlweer/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ class MainActivity : AppCompatActivity() {
R.id.nav_knmi_text -> {
fragment<KnmiTextFragment>(item.navId) {label = getString(item.stringId) }
}
R.id.nav_knmi_sixdayforecast -> {
fragment<KnmiSixDayForecastFragment>(item.navId) {label = getString(item.stringId) }
}
R.id.nav_knmi_pluim -> {
fragment<KnmiPluimFragment>(item.navId) {label = getString(item.stringId) }
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/foss/cnugteren/nlweer/MapData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ val KNMI_ITEMS = arrayOf(
MapData(R.string.menu_knmi_rain_m1_no_temp, R.id.nav_knmi_rain_m1_no_temp, 0, R.id.knmi_menu, "https://cdn.knmi.nl/knmi/map/page/weer/actueel-weer/neerslagradar/WWWRADAR_loop.gif", 425, 445, arrayOf(50.60f, 1.85f, 54.05f, 7.20f, -0.10f, 0.09f)),
// Special maps
MapData(R.string.menu_knmi_text, R.id.nav_knmi_text, 1, R.id.knmi_menu, "N/A", 0, 0, arrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)),
MapData(R.string.menu_knmi_sixdayforecast, R.id.nav_knmi_sixdayforecast, 1, R.id.knmi_menu, "N/A", 0, 0, arrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)),
MapData(R.string.menu_knmi_pluim, R.id.nav_knmi_pluim, 0, R.id.knmi_menu, "N/A", 0, 0, arrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f))
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class BuienradarChartFragment : Fragment() {
var background = Color.WHITE
if (darkMode == "dark_mode_yes") {
textColour = Color.WHITE
background = Color.rgb(46, 46, 46) // matches Android's dark mode colours
background = Color.rgb(48, 48, 48) // matches Android's dark mode colours
}

val chart = binding.buienradarChart
Expand Down Expand Up @@ -154,6 +154,11 @@ class BuienradarChartFragment : Fragment() {

// When complete: parses the result
override fun onPostExecute(htmlDocument: Document?) {
// It may be that the user has already moved on to the next fragment
if (_binding == null) {
return
}

val chart = binding.buienradarChart
if (htmlDocument == null || htmlDocument.text() == "") {
chart.setNoDataText(getString(R.string.menu_buienradar_chart_error) + ": " +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package foss.cnugteren.nlweer.ui.fragments

import android.content.res.Resources
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.URLUtil
import androidx.core.view.marginEnd
import androidx.core.view.marginStart
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import foss.cnugteren.nlweer.MainActivity
import foss.cnugteren.nlweer.R
import foss.cnugteren.nlweer.databinding.FragmentKnmiSixdayforecastBinding
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min

class KnmiSixDayForecastFragment : Fragment() {

private var _binding: FragmentKnmiSixdayforecastBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentKnmiSixdayforecastBinding.inflate(inflater, container, false)

// Pull down to refresh the page
val root = binding.root
val pullToRefresh = root.findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
pullToRefresh.setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener {
refreshPage()
pullToRefresh.isRefreshing = false
})

// Do display floating navigation buttons
val activity = this.activity as MainActivity
activity.toggleNavigationButtons(true)

// The web-viewer for the content
val webView = binding.webView
webView.settings.javaScriptEnabled = true

loadPage()

return root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

private fun getURL(): String {
return "https://www.knmi.nl/nederland-nu/weer/verwachtingen"
}

private fun refreshPage() {
val webView = binding.webView
webView.clearCache(false)
loadPage()
}

private fun loadPage() {
val webView = binding.webView
val htmlBuilder = HtmlBuilder()
webView.loadData(htmlBuilder.buildHtmlPageWithLoadingMessage(), "text/html", "utf-8")
RetrieveWebPage().execute(getURL())
}

internal inner class RetrieveWebPage : AsyncTask<String, Void, Document>() {

// Retrieves the data from the URL using JSoup (async)
@Deprecated("Deprecated in Java")
override fun doInBackground(vararg urls: String): Document? {
try {
return Jsoup.connect(urls[0]).get()
} catch (e: Exception) {
return null
}
}

// When complete: parses the result
@Deprecated("Deprecated in Java")
override fun onPostExecute(htmlDocument: Document?) {
// It may be that the user has already moved on to the next fragment
if (_binding == null) {
return
}

val webView = binding.webView
val htmlBuilder = HtmlBuilder()
if (htmlDocument == null) {
webView.loadData(htmlBuilder.buildHtmPageWithLoadingError(), "text/html", "utf-8")
return
}

val tableWrapperElement = htmlDocument.select("div.weather-map__table-wrp").firstOrNull()
if (tableWrapperElement == null){
webView.loadData(htmlBuilder.buildHtmPageWithLoadingError(), "text/html", "utf-8")
return
}

val tableData = getTableData(tableWrapperElement)
val htmlPageToShow = htmlBuilder.buildHtmlPageWithTables(tableData)
webView.loadData(htmlPageToShow, "text/html", "UTF-8")
}

// Get the weather data per day of the week
private fun getTableData(tableWrapperElement: Element) : Array<Array<String>> {
val weatherPerDay = tableWrapperElement.select("li")
val tableData = Array<Array<String>>(weatherPerDay.size, { Array<String>(15, {""}) })
weatherPerDay.forEachIndexed { colIndex, column ->
var rowIndex = 0

val dayOfTheWeek = column.selectFirst("strong.weather-map__table-cell")
?.text()
if (dayOfTheWeek != null) {
tableData[colIndex][rowIndex] = dayOfTheWeek
rowIndex++
}

column.select("span.weather-map__table-cell").forEach { rowItem ->
// If cell contains image, get the src link
val imageItem = rowItem.selectFirst("img")
if (imageItem != null) {
tableData[colIndex][rowIndex] = imageItem.attr("src")
rowIndex++
}
else {
// Item contains just text; split into header and data, if applicable
rowItem.text().split(' ', ignoreCase = false, limit = 2).forEach { item ->
tableData[colIndex][rowIndex] = item
rowIndex++
}
}
}
}

return tableData
}
}

internal inner class HtmlBuilder {

// Width in pixels of column containing KNMI weather data
private val columnWidth get() = 110

// Image size in pixels
private val imageSize get() = 54

// Font size to be used in HTML; in pixels
private val fontSize get() = 14

// Row height used for every other row for aesthetic reasons; in pixels
private val paddedRowHeight get() = 24

fun buildHtmlPageWithTables(tableData: Array<Array<String>>) : String {
val columnsPerTable = calculateColumnsPerTable(tableData)
val tablesHtml = getHtmlTables(tableData, columnsPerTable)

return getHtmlPageWithContent(tablesHtml)
}

private fun calculateColumnsPerTable(tableData: Array<Array<String>>): Int {
val webView = binding.webView
val widthPx = Resources.getSystem().displayMetrics.widthPixels
val density = Resources.getSystem().displayMetrics.density
val effectiveWidth = floor((widthPx / density).toDouble());
val usableWidth = effectiveWidth - (webView.marginStart + webView.marginEnd) / density
val columnsPerRow = floor(usableWidth / columnWidth).toInt()

return min(columnsPerRow, tableData.size)
}

private fun getHtmlTables(tableData: Array<Array<String>>, columnsPerTable: Int) : String {
val numberOfTables = ceil(tableData.size.toDouble() / columnsPerTable).toInt()
val numberOfRowsPerTable = tableData[0].size
var htmlTable = ""
var totalNumberOfColumns = tableData.size

for (tableNumber in 0..<numberOfTables) {
htmlTable += """
<table>
<colgroup>
<col style="min-width:""".trimIndent() + columnWidth + """px" span="""" + totalNumberOfColumns + """" />
</colgroup>""".trimMargin()

for (row in 0..<numberOfRowsPerTable) {
htmlTable += """<tr>"""
var column = tableNumber * columnsPerTable
// Loop until either all columns for the current table have been processed,
// or all columns have already been processed
while (column < (tableNumber + 1 ) * columnsPerTable && column < totalNumberOfColumns) {
var content = tableData[column][row]
if (URLUtil.isValidUrl(content)) {
content = """<img alt="" src="""" + content + """" width="""" + imageSize + """px"/>"""
}
htmlTable += "<td>$content</td>"

column++
}
htmlTable += """</tr>"""
}

htmlTable += "</table>"
htmlTable += "<p></p>"
// Add additional spacing between tables
if (tableNumber < numberOfTables - 1) {
htmlTable += """<br style="line-height: 10px">"""
}
}

return htmlTable
}

fun buildHtmlPageWithLoadingMessage() : String {
val loadingMessageHtml = "<p>" + getString(R.string.menu_knmi_text_loading) + "</p>" + """<br style="line-height: 10px">"""
return getHtmlPageWithContent(loadingMessageHtml)
}

fun buildHtmPageWithLoadingError() : String {
val errorMessageHtml = "<p>" + getString(R.string.menu_knmi_text_failed) + "</p>" + """<br style="line-height: 10px">"""
return getHtmlPageWithContent(errorMessageHtml)
}

private fun getHtmlPageWithContent(content: String) : String {
val webView = binding.webView
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(webView.context)
val darkMode = sharedPreferences.getString("dark_mode", "dark_mode_no")
var backgroundColor = "rgb(250, 250, 250)"
var textColor = "black"
if (darkMode == "dark_mode_yes") {
backgroundColor = "rgb(48, 48, 48)" // Android dark mode color
textColor = "rgb(193, 193, 193)" // Android dark mode color
}

return """<html>
<head>
<style type='text/css'>
body {
font-size: """.trimIndent() + fontSize + """px;
color:""".trimIndent() + textColor + """;
background-color:""".trimIndent() + backgroundColor + """;
}
tr {
font-size: """.trimIndent() + fontSize + """px;
}
tr:nth-child(1) {
font-weight: bold;
color:""".trimIndent() + textColor + """;
}
tr:nth-child(2n) {
color: grey;
}
tr:nth-child(2n+3) {
height: """.trimIndent() + paddedRowHeight + """px;
vertical-align: text-top;
color:""".trimIndent() + textColor + """;
}
</style>
</head>
<body>""" + content + """
<p>""".trimIndent() + getString(R.string.menu_knmi_text_source) + """</p>
</body>""".trimIndent()
}
}
}
Loading

0 comments on commit ead8b06

Please sign in to comment.