Commit 9cd2c754 authored by Daniel Sonck's avatar Daniel Sonck
Browse files

[THFMA-30] [THFMA-31] [THFMA-32] Version checking

- Updated translations
- Added changelog window
- Prevent launching a deprecated version
- Show update dialog once when new version is released
parent 4625f19c
......@@ -62,6 +62,9 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
implementation 'com.android.volley:volley:1.1.0'
implementation project(':changelog')
}
// The sample build uses multiple directories to
......@@ -74,11 +77,11 @@ List<String> dirs = [
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
targetSdkVersion 29
applicationId "fm.touhou.touhoufm"
versionCode gitVersionCode
......
......@@ -35,9 +35,16 @@
package fm.touhou.touhoufm.ui
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.drawerlayout.widget.DrawerLayout
......@@ -45,24 +52,39 @@ import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.*
import ch.derlin.changelog.Changelog
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import com.google.android.material.navigation.NavigationView
import fm.touhou.touhoufm.BuildConfig
import fm.touhou.touhoufm.R
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration : AppBarConfiguration
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var queue: RequestQueue
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
queue = Volley.newRequestQueue(this)
val prefs = getSharedPreferences("AppDetails", Context.MODE_PRIVATE);
setContentView(R.layout.navigation_activity)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? ?: return
val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
?: return
val navController = host.navController
val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
val drawerLayout: DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.home_dest),
drawerLayout)
......@@ -70,8 +92,77 @@ class MainActivity : AppCompatActivity() {
setupActionBar(navController, appBarConfiguration)
setupNavigationMenu(navController)
val appVersion = BuildConfig.VERSION_CODE
val lastVersion = 42//prefs.getInt("LatestVersion", 0)
val lastKnownVersion = prefs.getInt("LatestReleaseVersion", 0)
val jsonRequest = JsonObjectRequest("https://www.touhou.fm/app-release.json", null,
Response.Listener { response: JSONObject ->
val supported: Boolean;
when {
appVersion < response.getInt("supported") -> {
supported = false;
AlertDialog.Builder(this)
.setTitle("Unsupported app version")
.setMessage("This version of the app is currently unsupported, please download the new version from the play store")
.setPositiveButton("Download", DialogInterface.OnClickListener { dialogInterface, i ->
val appPackageName = packageName
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appPackageName")))
} catch (anfe: android.content.ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName")))
}
})
.setNegativeButton("Quit", DialogInterface.OnClickListener { dialogInterface, i ->
this@MainActivity.finish()
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
appVersion < response.getInt("beta") - 1 -> {
supported = true;
if (lastVersion != appVersion || lastKnownVersion != response.getInt("beta") - 1) {
AlertDialog.Builder(this)
.setTitle("New version")
.setMessage("A new version is available in the play store")
.setPositiveButton("Download", DialogInterface.OnClickListener { dialogInterface, i ->
val appPackageName = packageName
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appPackageName")))
} catch (anfe: android.content.ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName")))
}
})
.setNegativeButton(android.R.string.cancel, null)
.setIcon(android.R.drawable.ic_dialog_info)
.show();
}
}
appVersion < response.getInt("alpha") -> {
supported = true;
Toast.makeText(this, "Thanks for being a beta tester", LENGTH_LONG).show()
}
else -> {
supported = true;
Toast.makeText(this, "Thanks for being an alpha tester", LENGTH_LONG).show()
}
}
if (supported && lastVersion != appVersion) {
prefs.edit().putInt("LatestVersion", appVersion).apply()
Changelog.createDialog(this, versionCode = lastVersion).show()
}
}, Response.ErrorListener {
Toast.makeText(this, "Failed to check latest version", Toast.LENGTH_LONG).show();
})
queue.add(jsonRequest);
}
private fun setupNavigationMenu(navController: NavController) {
val sideNavView = findViewById<NavigationView>(R.id.nav_view)
sideNavView.setupWithNavController(navController)
......@@ -80,7 +171,7 @@ class MainActivity : AppCompatActivity() {
private fun setupActionBar(navController: NavController,
appBarConfig : AppBarConfiguration) {
appBarConfig: AppBarConfiguration) {
setupActionBarWithNavController(navController, appBarConfig)
}
......
This application tunes into the TouHou.FM radio. This radio streams original and doujin music from Touhou Project by TeamShanghaiAlice.
Zurzeit ist es eine Alpha-Version, in welcher nicht Alles implementiert ist.
Unter anderem lassen sich auch einige Bugs bzw. Fehler finden.
Ich bitte Sie eine Nachricht für alle Fehler, Anregungen, positives oder negatives Feedback, an app@touhou.fm zu senden.
\ No newline at end of file
Hören Sie TouHow.FM direkt von ihrem Handy, Tablet oder allen anderen Geräten.
\ No newline at end of file
Radio Spieler von TouHou.FM
\ No newline at end of file
Fixed Metadatei informationen
Stabiele weiterentwicklung
Widget zugefügt
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright 2018 Daniel Sonck, translation Patrick Büchele
*
* This file is part of fm.touhou.touhoufm.
*
* fm.touhou.touhoufm is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*
* fm.touhou.touhoufm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with fm.touhou.touhoufm. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">TouHou.FM</string>
<string name="label_stop">Stop</string>
<string name="label_pause">Pause</string>
<string name="label_play">Abspielen</string>
<string name="label_play_pause">Pause und Abspielen umschalten</string>
<string name="time_format">%2$d:%1$02d</string><!-- MM:SS -->
<string name="album_format">( %1$s )</string><!-- ( <album name> ) -->
<string name="artist_circle_format">%1$s ( %2$s )</string><!-- <artist name> ( <circle name> ) -->
<string name="unknown_song">Unbekantes Lied</string>
<string name="unknown_album">Unbekantes Album</string>
<string name="unknown_artist">Umbekanter Künstler</string>
<string name="unknown_circle">Unbekanter Kreise</string>
<string name="home">Home</string>
<string name="settings">Einstellungen</string>
<string name="stream_port_summary">Welchen Port benutzen fürs streaming</string>
<string name="stream_port_title">Streaming Port</string>
<string name="stream_custom_title">Benutze benutzerdefinierten stream server</string>
<string name="stream_host_summary">Welche Adresse fürs Streaming benutzt wird</string>
<string name="stream_host_title">Streaming Host</string>
<string name="category_advanced_summary">Einstellungen für Power User</string>
<string name="category_advanced_title">Fortgeschritten</string>
<string name="keep_metadata_alive_summary">Lass die Verbindung offen fürs erhalten von Metadata Updates. Es braucht Daten, auch wenn der Vorgang gestoppt ist.</string>
<string name="keep_metadata_alive_title">Immer Fordern nach metadata</string>
<string name="show_data_summary">Zeigt den Datenverbrauch vom streaming</string>
<string name="show_data_title">Datenverbrauch</string>
<string name="streaming_rate_title">Streaming Bitrate</string>
<string name="streaming_rate_summary">Maximal verfügbare Bitrate fürs Streaming</string>
<string name="category_streaming_title">Streaming</string>
<string name="category_streaming_summary">Feinjustieren für die Streamingqualität und den Datenverbrauch</string>
</resources>
\ No newline at end of file
......@@ -40,5 +40,6 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="changelogStyle">@style/LibChangelog</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<changelog >
<release version="v2.0.19" versioncode="44">
<change>Updated translations, added German</change>
<change>Add this changelog window</change>
<change>Notify users when version is outdated, prevent launch when unsupported</change>
<change>Fix crash on pause</change>
<change>Show song progress information</change>
<change>Expert: Add ability to pick a streaming server</change>
<change>Widget support</change>
<change>Update icon to allow launchers to use their style</change>
</release>
<release version="v2.0.18" versioncode="33">
<change>Update to new streaming specifications</change>
<change>Song info works again</change>
<change>Fixed code causing crashes</change>
</release>
<release version="v2.0.13" versioncode="27">
<change>Updated translations</change>
<change>Fix memoryleak</change>
</release>
<release version="v2.0.12" versioncode="24">
<change>Fix crash on startup</change>
</release>
<release version="v2.0.11" versioncode="23">
<change>THFMA-8: Using app Quit will remove notification as well</change>
<change>THFMA-9: Add information and playback buttons to the notification</change>
<change>THFMA-10: Fix app not stopping playback immediately</change>
<change>THFMA-11: Fix music sounding slow after switch from Data to Wifi</change>
<change>THFMA-12: Integrate better with android. App will advertise it's playing and automatically stop if another source is starting (e.g. youtube)</change>
</release>
<release version="v2.0.10" versioncode="22">
<change>Small compatibility fixes</change>
</release>
<release version="v2.0.9" versioncode="21">
<change>Improved stability</change>
</release>
<release version="v2.0.8" versioncode="20">
<change>Fixed endless asking for power management</change>
</release>
<release version="v2.0.7" versioncode="19">
<change>Fix battery optimization detection</change>
</release>
<release version="v2.0.6" versioncode="18">
<change>Added dialog to disable optimisations for the app which can cause random issues during streaming while screen is off</change>
<change>Updated translations</change>
</release>
<release version="v2.0.5" versioncode="17">
<change>Fixed crashing bug</change>
</release>
<release version="v2.0.4" versioncode="16">
<change>Added translations: French and Hungarian</change>
<change>Fixed bug causing crash when no network is available</change>
</release>
<release version="v2.0.3" versioncode="15">
<change>Removed failing login UI</change>
<change>Removed unused settings screen</change>
</release>
<release version="v2.0.0" versioncode="12">
<change>New streaming technology improving reliability and quality especially on roaming networks</change>
</release>
</changelog>
\ No newline at end of file
......@@ -4,7 +4,7 @@
<PreferenceCategory
android:summary="@string/category_streaming_summary"
android:title="@string/category_streaming_title"
app:isPreferenceVisible="false">
app:isPreferenceVisible="true">
<ListPreference
android:defaultValue="-1"
......@@ -27,12 +27,7 @@
</PreferenceCategory>
<PreferenceCategory
android:summary="@string/category_advanced_summary"
android:title="@string/category_advanced_title"
app:initialExpandedChildrenCount="0">
<SwitchPreference
android:defaultValue="false"
android:key="stream_custom"
android:title="@string/stream_custom_title" />
android:title="@string/category_advanced_title">
<EditTextPreference
android:defaultValue="strm.touhou.fm"
android:key="stream_host"
......@@ -49,5 +44,9 @@
android:summary="@string/stream_port_summary"
android:title="@string/stream_port_title"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:defaultValue="false"
android:key="stream_custom"
android:title="@string/stream_custom_title" />
</PreferenceCategory>
</PreferenceScreen>
\ No newline at end of file
......@@ -23,9 +23,9 @@ import android.util.Log
import fm.touhou.touhoufm.radio.RingBuffer
import io.mockk.every
import io.mockk.mockkStatic
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.*
/**
......@@ -33,20 +33,16 @@ import java.util.*
*/
class RingBufferTest {
internal inner class Reader(private val buff: RingBuffer, size: Int) : Thread() {
var out: ShortArray
var out: ShortArray = ShortArray(size)
var readReturn: Int = 0
init {
out = ShortArray(size)
}
override fun run() {
readReturn = buff.read(out, out.size)
}
}
@Before
@BeforeEach
fun setupMocks() {
mockkStatic(Log::class)
every { Log.v(any(), any()) } returns 0
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 2
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "com.google.android.material:material:1.1.0-alpha05"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0-alpha03'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha03'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package ch.derlin.changelog;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("ch.derlin.changelog.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ch.derlin.changelog" />
package ch.derlin.changelog
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.XmlResourceParser
import android.text.format.DateFormat
import android.view.View
import android.widget.TextView
import androidx.core.content.pm.PackageInfoCompat.getLongVersionCode
import androidx.recyclerview.widget.RecyclerView
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
import java.sql.Date
import java.text.ParseException
/**
* Changelog
*
* Copyright (C) 2018 Lucy Linder (derlin)
*
* This software may be modified and distributed under the terms
* of the Apache 2.0 license. See the LICENSE file for details.
*/
/**
* Updated to AndroidX: Copyright (C) 2019 Arne Rantzen (Tyxz)
*/
object Changelog {
/** Use this value if you want all the changelog (i.e. all the release entries) to appear. */
val ALL_VERSIONS = 0
/** Constants for xml tags and attributes (see res/xml/changelog.xml for an example) */
object XmlTags {
val RELEASE = "release"
val ITEM = "change"
val VERSION_NAME = "version"
val VERSION_CODE = "versioncode"
val SUMMARY = "summary"
val DATE = "date"
val TYPE ="type"
}
/**
* Create a dialog displaying the changelog.
* @param ctx The calling activity
* @param versionCode Define the oldest version to show. In other words, the dialog will contains
* release entries with a `versionCode` attribute >= [versionCode]. Default to all.
* @param title The title of the dialog. Default to "Changelog"
* @param resId The resourceId of the xml file, default to `R.xml.changelog`
*/
@Throws(XmlPullParserException::class, IOException::class)
fun createDialog(ctx: Activity, versionCode: Int = ALL_VERSIONS,
title: String? = null, resId: Int = R.xml.changelog): AlertDialog {
return AlertDialog.Builder(ctx)
.setView(createChangelogView(ctx, versionCode, title, resId))
.setPositiveButton("OK") { _, _ -> }
.create()
}
/**
* Create a custom view with the changelog list.
* This is the view that is displayed in the dialog on a call to [createDialog].
* See [createDialog] for the parameters.
*/
@Throws(XmlPullParserException::class, IOException::class)
fun createChangelogView(ctx: Activity, versionCode: Int = ALL_VERSIONS,
title: String? = null, resId: Int = R.xml.changelog): View {
val view = ctx.layoutInflater.inflate(R.layout.changelog, null)
val changelog = loadChangelog(ctx, resId, versionCode)
title?.let { view.findViewById<TextView>(R.id.changelog_title).text = it }
view.findViewById<RecyclerView>(R.id.recyclerview).adapter = ChangelogAdapter(changelog)
return view
}
/**
* Extension function to retrieve the current version of the application from the package.
* @return a pair <versionName, versionCode> (as set in the build.gradle file). Example: <"1.1", 3>
*/
@Throws(PackageManager.NameNotFoundException::class)
fun Activity.getAppVersion(): Pair<Long, String> {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
return Pair(getLongVersionCode(packageInfo), packageInfo.versionName)
}
// -----------------------------------------
/**
* Read the changelog.xml and create a list of [ChangelogItem] and [ChangelogHeader].
* @param context: the calling activity
* @param resourceId: the name of the changelog file, default to R.xml.changelog
* @param version: the lowest release to display (see [createDialog] for more details)
* @return the list of [ChangelogItem], in the order of the [resourceId] file (most to less recent)
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun loadChangelog(context: Activity, resourceId: Int = R.xml.changelog, version: Int = ALL_VERSIONS):
MutableList<ChangelogItem> {
val clList = mutableListOf<ChangelogItem>()
val xml = context.resources.getXml(resourceId)
try {
while (xml.eventType != XmlPullParser.END_DOCUMENT) {
if (xml.eventType == XmlPullParser.START_TAG && xml.name == XmlTags.RELEASE) {
val releaseVersion = Integer.parseInt(xml.getAttributeValue(null, XmlTags.VERSION_CODE))
if (releaseVersion >= version) {
clList.addAll(parseReleaseTag(context, xml))
} else
xml.next()
} else {
xml.next()
}
}
} finally {