mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
mobile: Update dependencies and adapt to changes
This commit is contained in:
parent
b14dcd67f2
commit
8752ac81dd
15 changed files with 326 additions and 826 deletions
|
|
@ -26,23 +26,27 @@ apply plugin: 'kotlin-android'
|
|||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.johrpan.musicus"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -50,8 +54,6 @@ android {
|
|||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
// See https://github.com/ryanheise/audio_service/blob/master/README.md#android-setup
|
||||
shrinkResources false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +64,4 @@ flutter {
|
|||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.johrpan.musicus">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- TODO: Actually manage obtaining this permission -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:name="${applicationName}"
|
||||
android:label="Musicus"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
|
|
@ -25,13 +31,21 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name="com.ryanheise.audioservice.AudioService">
|
||||
<service
|
||||
android:name="com.ryanheise.audioservice.AudioService"
|
||||
android:exported="true"
|
||||
tools:ignore="Instantiatable">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<receiver
|
||||
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="Instantiatable">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
|
|
|
|||
|
|
@ -1,240 +1,12 @@
|
|||
package de.johrpan.musicus
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.annotation.NonNull
|
||||
import android.content.Context
|
||||
import com.ryanheise.audioservice.AudioServicePlugin
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
|
||||
class Document(private val id: String, private val name: String, private val parentId: String?, private val isDirectory: Boolean) {
|
||||
fun toMap(): Map<String, Any?> {
|
||||
return mapOf(
|
||||
"id" to id,
|
||||
"name" to name,
|
||||
"parentId" to parentId,
|
||||
"isDirectory" to isDirectory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val CHANNEL = "de.johrpan.musicus/platform"
|
||||
private val AODT_REQUEST = 0
|
||||
|
||||
private var aodtResult: MethodChannel.Result? = null
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||
if (call.method == "openTree") {
|
||||
aodtResult = result
|
||||
// We will get the result within onActivityResult
|
||||
openTree()
|
||||
} else if (call.method == "getChildren") {
|
||||
val treeUri = Uri.parse(call.argument<String>("treeUri"))
|
||||
val parentId = call.argument<String>("parentId")
|
||||
val children = getChildren(treeUri, parentId)
|
||||
result.success(children.map { it.toMap() })
|
||||
} else if (call.method == "readFile") {
|
||||
val treeUri = Uri.parse(call.argument<String>("treeUri"))
|
||||
val id = call.argument<String>("id")!!
|
||||
result.success(readFile(treeUri, id))
|
||||
} else if (call.method == "getUriByName") {
|
||||
val treeUri = Uri.parse(call.argument<String>("treeUri"))
|
||||
val parentId = call.argument<String>("parentId")!!
|
||||
val fileName = call.argument<String>("fileName")!!
|
||||
result.success(getUriByName(treeUri, parentId, fileName).toString())
|
||||
} else if (call.method == "readFileByName") {
|
||||
val treeUri = Uri.parse(call.argument<String>("treeUri"))
|
||||
val parentId = call.argument<String>("parentId")!!
|
||||
val fileName = call.argument<String>("fileName")!!
|
||||
result.success(readFileByName(treeUri, parentId, fileName))
|
||||
} else if (call.method == "writeFileByName") {
|
||||
val treeUri = Uri.parse(call.argument<String>("treeUri"))
|
||||
val parentId = call.argument<String>("parentId")!!
|
||||
val fileName = call.argument<String>("fileName")!!
|
||||
val content = call.argument<String>("content")!!
|
||||
writeFileByName(treeUri, parentId, fileName, content)
|
||||
result.success(null)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (requestCode == AODT_REQUEST) {
|
||||
if (resultCode == Activity.RESULT_OK && data?.data != null) {
|
||||
// Drop all old URIs
|
||||
contentResolver.persistedUriPermissions.forEach {
|
||||
contentResolver.releasePersistableUriPermission(it.uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
|
||||
// We already checked for null
|
||||
val uri = data.data!!
|
||||
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
aodtResult?.success(uri.toString())
|
||||
} else {
|
||||
aodtResult?.success(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a document tree using the storage access framework
|
||||
*
|
||||
* The result is handled within [onActivityResult]
|
||||
*/
|
||||
private fun openTree() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
|
||||
startActivityForResult(intent, AODT_REQUEST)
|
||||
}
|
||||
|
||||
/**
|
||||
* List children of a directory
|
||||
*
|
||||
* @param treeUri The treeUri from the ACTION_OPEN_DOCUMENT_TREE request
|
||||
* @param parentId Document ID of the parent directory or null for the top level directory
|
||||
* @return List of directories and files within the directory
|
||||
*/
|
||||
private fun getChildren(treeUri: Uri, parentId: String?): List<Document> {
|
||||
val realParentId = parentId ?: DocumentsContract.getTreeDocumentId(treeUri)
|
||||
val children = ArrayList<Document>()
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, realParentId)
|
||||
|
||||
val cursor = contentResolver.query(
|
||||
childrenUri,
|
||||
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE),
|
||||
null, null, null)
|
||||
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getString(0)
|
||||
val name = cursor.getString(1)
|
||||
val isDirectory = cursor.getString(2) == DocumentsContract.Document.MIME_TYPE_DIR
|
||||
|
||||
// Use parentId here to let the consumer know that we are at the top level.
|
||||
children.add(Document(id, name, parentId, isDirectory))
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a file by name
|
||||
*
|
||||
* @param treeUri The treeUri from the ACTION_OPEN_DOCUMENT_TREE request
|
||||
* @param parentId The directory in which the file is searched for
|
||||
* @param fileName Name of the file
|
||||
* @return The URI of the file or null
|
||||
*/
|
||||
private fun getUriByName(treeUri: Uri, parentId: String, fileName: String): Uri? {
|
||||
var uri: Uri? = null
|
||||
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, parentId)
|
||||
val projection = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
||||
|
||||
// The file system provider doesn't support a select clause.
|
||||
val cursor = contentResolver.query(childrenUri, projection, null, null, null)
|
||||
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getString(0)
|
||||
val name = cursor.getString(1)
|
||||
|
||||
if (name == fileName) {
|
||||
uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content of a file
|
||||
*
|
||||
* @param treeUri The URI from ACTION_OPEN_DOCUMENT_TREE
|
||||
* @param id The document ID of the file
|
||||
* @return File content or null
|
||||
*/
|
||||
private fun readFile(treeUri: Uri, id: String): String? {
|
||||
val uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, id)
|
||||
|
||||
// TODO: Handle errors.
|
||||
val input = contentResolver.openInputStream(uri)!!
|
||||
val result = input.reader().readText()
|
||||
input.close()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content of a file by name
|
||||
*
|
||||
* @param treeUri The URI from ACTION_OPEN_DOCUMENT_TREE
|
||||
* @param parentId Document ID of the parent directory
|
||||
* @param fileName Name of the file
|
||||
* @return File content or null
|
||||
*/
|
||||
private fun readFileByName(treeUri: Uri, parentId: String, fileName: String): String? {
|
||||
var uri = getUriByName(treeUri, parentId, fileName)
|
||||
|
||||
return if (uri != null) {
|
||||
// TODO: Handle errors.
|
||||
val input = contentResolver.openInputStream(uri)!!
|
||||
val result = input.reader().readText()
|
||||
input.close()
|
||||
|
||||
return result
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to file by name
|
||||
*
|
||||
* The file will always have the MIME type application/json.
|
||||
*
|
||||
* @param treeUri The URI from ACTION_OPEN_DOCUMENT_TREE
|
||||
* @param parentId Document ID of the parent directory
|
||||
* @param fileName Name of the file
|
||||
* @param content Content to write
|
||||
* @return File content or null
|
||||
*/
|
||||
private fun writeFileByName(treeUri: Uri, parentId: String, fileName: String, content: String) {
|
||||
var uri = getUriByName(treeUri, parentId, fileName);
|
||||
|
||||
if (uri == null) {
|
||||
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, parentId)
|
||||
uri = DocumentsContract.createDocument(contentResolver, parentUri, "application/json", fileName)
|
||||
}
|
||||
|
||||
// TODO: Handle errors.
|
||||
val output = contentResolver.openOutputStream(uri!!)!!;
|
||||
val writer = output.writer()
|
||||
writer.write(content)
|
||||
writer.close()
|
||||
output.close()
|
||||
override fun provideFlutterEngine(context: Context): FlutterEngine {
|
||||
return AudioServicePlugin.getFlutterEngine(context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
mobile/android/app/src/main/res/raw/keep.xml
Normal file
3
mobile/android/app/src/main/res/raw/keep.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/*" />
|
||||
Loading…
Add table
Add a link
Reference in a new issue