diff --git a/Source/GUI/Android/app/CMakeLists.txt b/Source/GUI/Android/app/CMakeLists.txt index b350038c5..64f3a1b9c 100644 --- a/Source/GUI/Android/app/CMakeLists.txt +++ b/Source/GUI/Android/app/CMakeLists.txt @@ -10,6 +10,8 @@ set(BUILD_SHARED_LIBS ON CACHE BOOL "Force shared library build" FORCE) set(LARGE_FILES OFF CACHE BOOL "Enable large files support" FORCE) set(BUILD_ZENLIB ON CACHE BOOL "Build bundled ZenLib" FORCE) +find_package(curl REQUIRED CONFIG) + add_subdirectory(../../../../../MediaInfoLib/Project/CMake ${CMAKE_CURRENT_BINARY_DIR}/MediaInfoLib) target_link_options(mediainfo diff --git a/Source/GUI/Android/app/build.gradle b/Source/GUI/Android/app/build.gradle index 54a110d4f..b84f3eeb2 100644 --- a/Source/GUI/Android/app/build.gradle +++ b/Source/GUI/Android/app/build.gradle @@ -14,7 +14,7 @@ android { defaultConfig { applicationId "net.mediaarea.mediainfo" multiDexEnabled = true - minSdkVersion 21 + minSdkVersion 23 versionCode 59 versionName "26.05" targetSdkVersion 37 @@ -32,20 +32,18 @@ android { "-DMEDIAINFO_DUPLICATE=OFF", "-DMEDIAINFO_DVDIF_ANALYZE=OFF", "-DMEDIAINFO_EVENTS=OFF", + "-DMEDIAINFO_FILE=OFF", "-DMEDIAINFO_FILTER=OFF", "-DMEDIAINFO_FIXITY=OFF", "-DMEDIAINFO_GRAPH=OFF", "-DMEDIAINFO_IBI=OFF", - "-DMEDIAINFO_LIBCURL=OFF", "-DMEDIAINFO_LIBMMS=OFF", "-DMEDIAINFO_MACROBLOCKS=OFF", "-DMEDIAINFO_MD5=OFF", "-DMEDIAINFO_NEXTPACKET=OFF", - "-DMEDIAINFO_READER=OFF", "-DMEDIAINFO_READTHREAD=OFF", "-DMEDIAINFO_REFERENCES=OFF", "-DMEDIAINFO_SHA1=OFF", - "-DMEDIAINFO_SHA2=OFF", "-DMEDIAINFO_TRACE=OFF", "-DMEDIAINFO_TRACE_FFV1CONTENT=OFF" } @@ -77,6 +75,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + buildFeatures { + prefab = true + } externalNativeBuild { cmake { path "CMakeLists.txt" @@ -202,9 +203,9 @@ tasks.register('copyLocales', DefaultTask) { preBuild.dependsOn copyLocales dependencies { - implementation 'com.google.android.material:material:1.13.0' // can only upgrade when minSdkVersion >= 23 + implementation 'com.google.android.material:material:1.14.0' implementation 'androidx.appcompat:appcompat:1.7.1' - implementation "androidx.core:core-ktx:1.17.0" // can only upgrade when minSdkVersion >= 23 + implementation "androidx.core:core-ktx:1.18.0" implementation 'androidx.documentfile:documentfile:1.1.0' implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' @@ -213,19 +214,19 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' // Android Room - // can only upgrade when minSdkVersion >= 23 - implementation 'androidx.room:room-runtime:2.7.2' - implementation 'androidx.room:room-ktx:2.7.2' - ksp 'androidx.room:room-compiler:2.7.2' + implementation 'androidx.room:room-runtime:2.8.4' + implementation 'androidx.room:room-ktx:2.8.4' + ksp 'androidx.room:room-compiler:2.8.4' // Android Lifecycle implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' ksp "androidx.lifecycle:lifecycle-common-java8:2.10.0" // Google Billing - // can only upgrade when minSdkVersion >= 23 - implementation "com.android.billingclient:billing:8.0.0" - implementation "com.android.billingclient:billing-ktx:8.0.0" + implementation "com.android.billingclient:billing:9.0.0" + implementation "com.android.billingclient:billing-ktx:9.0.0" // KotlinX Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0" // Locales manager implementation 'com.github.YarikSOffice:lingver:1.3.0' + // Curl static build from https://github.com/vvb2060/curl-android + implementation "io.github.vvb2060.ndk:curl:8.18.0" } diff --git a/Source/GUI/Android/app/src/main/AndroidManifest.xml b/Source/GUI/Android/app/src/main/AndroidManifest.xml index 1f3ed1ac9..59923717f 100644 --- a/Source/GUI/Android/app/src/main/AndroidManifest.xml +++ b/Source/GUI/Android/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + + diff --git a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/Core.kt b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/Core.kt index 9291f5c58..2da2109a7 100644 --- a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/Core.kt +++ b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/Core.kt @@ -6,6 +6,8 @@ package net.mediaarea.mediainfo +import android.net.Uri + object Core { data class ReportView(val name: String, val desc: String, val mime: String, val exportable: Boolean) { override fun toString(): String { @@ -47,6 +49,21 @@ object Core { return report.toByteArray() } + @Synchronized + fun createReport(networkUri: Uri): ByteArray { + mi.Option("Language", "") + mi.Option("Inform", "MIXML") + mi.Option("Input_Compressed", "") + mi.Option("Inform_Compress", "zlib+base64") + mi.Option("File_FileName") // Reset filename so that MediaInfoLib does not use previous filename + + mi.Open(networkUri.toString()) + val report: String = mi.Inform() + mi.Close() + + return report.toByteArray() + } + @Synchronized fun convertReport(report: ByteArray, format: String, export: Boolean = false) : String { mi.Option("Inform", format) diff --git a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/MediaInfo.kt b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/MediaInfo.kt index 839da2165..b2d5c0a04 100644 --- a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/MediaInfo.kt +++ b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/MediaInfo.kt @@ -43,7 +43,7 @@ class MediaInfo { external fun Init(): Long external fun Destroy(): Int private external fun OpenFd(fd: Int, name: String): Int - private external fun Open(name:String ): Int + external fun Open(name:String): Int fun Open(fd: Int, name: String): Int { return OpenFd(fd, name) } diff --git a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportListActivity.kt b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportListActivity.kt index d1d230391..8c9b5fb26 100644 --- a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportListActivity.kt +++ b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportListActivity.kt @@ -6,7 +6,7 @@ package net.mediaarea.mediainfo -import kotlin.jvm.* +import kotlin.math.abs import kotlinx.coroutines.launch import androidx.activity.result.contract.ActivityResultContracts @@ -27,9 +27,11 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager.getDefaultSharedPreferences import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.listitem.ListItemViewHolder import android.os.Build import android.os.Bundle @@ -43,12 +45,16 @@ import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources import android.provider.Settings -import android.view.* +import android.util.Patterns +import android.view.Menu +import android.view.View +import android.view.ViewConfiguration +import android.view.ViewGroup import com.yariksoffice.lingver.Lingver import kotlinx.coroutines.flow.collectLatest import java.io.BufferedReader -import java.util.* +import java.util.Locale import net.mediaarea.mediainfo.databinding.ActivityReportListBinding import net.mediaarea.mediainfo.databinding.ClearButtonBinding @@ -169,14 +175,12 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { private fun handleUri(uri: Uri, isMultiple: Boolean = false) { when (uri.scheme) { "file" -> { - if (Build.VERSION.SDK_INT >= 23) { - if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - pendingFileUris.add(uri) - ActivityCompat.requestPermissions(this@ReportListActivity, - arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), - READ_EXTERNAL_STORAGE_PERMISSION_REQUEST) - return - } + if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + pendingFileUris.add(uri) + ActivityCompat.requestPermissions(this@ReportListActivity, + arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), + READ_EXTERNAL_STORAGE_PERMISSION_REQUEST) + return } } "content" -> { @@ -234,7 +238,15 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { } Intent.ACTION_SEND -> { val uri = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (intent.type == "text/plain") { + intent.getStringExtra(Intent.EXTRA_TEXT)?.let { text -> + val matcher = Patterns.WEB_URL.matcher(text) + if (matcher.find()) + matcher.group().toUri() + else + null + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) } else { @Suppress("DEPRECATION") @@ -708,6 +720,25 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { private fun setupRecyclerView(recyclerView: RecyclerView) { recyclerView.adapter = ItemRecyclerViewAdapter() + val layoutManager = recyclerView.layoutManager as? LinearLayoutManager + val extFab = activityReportListBinding.addButton + val scaledTouchSlop = ViewConfiguration.get(recyclerView.context).scaledTouchSlop + + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (abs(dy) <= scaledTouchSlop) { + if (!extFab.isExtended && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) + extFab.extend() + return + } + if (dy > 0 && extFab.isExtended) + extFab.shrink() + else if (dy < 0 && !extFab.isExtended) + extFab.extend() + } + }) } sealed class ReportListItem { @@ -794,6 +825,7 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { is ReportListItem.ReportData -> { if (holder is ListViewHolder) { val report: Report = item.report + holder.bind(position, currentList.size - 1) // Last is button holder.name.text = report.filename holder.id = report.id with(holder.itemView) { @@ -805,6 +837,7 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { } is ReportListItem.ClearButton -> { if (holder is ButtonViewHolder) { + holder.bind(0, 1) with(holder.itemView) { setOnClickListener { reportModel.deleteAllReports() @@ -828,7 +861,7 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { } } - inner class ListViewHolder(binding: ReportListContentBinding) : RecyclerView.ViewHolder(binding.root) { + inner class ListViewHolder(binding: ReportListContentBinding) : ListItemViewHolder(binding.root) { val name: TextView = binding.nameText var id: Int = -1 @@ -840,7 +873,7 @@ class ReportListActivity : AppCompatActivity(), ReportActivityListener { } } - inner class ButtonViewHolder(val binding: ClearButtonBinding) : RecyclerView.ViewHolder(binding.root) + inner class ButtonViewHolder(val binding: ClearButtonBinding) : ListItemViewHolder(binding.root) } companion object { diff --git a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportViewModel.kt b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportViewModel.kt index d71373837..f90d56447 100644 --- a/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportViewModel.kt +++ b/Source/GUI/Android/app/src/main/java/net/mediaarea/mediainfo/ReportViewModel.kt @@ -58,6 +58,7 @@ class ReportViewModel(private val dataSource: ReportDao) : ViewModel() { for (uri in uris) { var fd: ParcelFileDescriptor? = null var displayName = "" + var isUrl = false when (uri.scheme) { "content" -> { @@ -94,17 +95,23 @@ class ReportViewModel(private val dataSource: ReportDao) : ViewModel() { displayName = uri.lastPathSegment.orEmpty() } + + "http", "https", "ftp", "sftp" -> { + isUrl = true + displayName = uri.lastPathSegment.orEmpty() + } } - if (fd == null) { + if (fd == null && !isUrl) { continue } - fd.use { - val report: ByteArray = Core.createReport(it.detachFd(), displayName) - lastInsertedId = - insertReport(Report(0, displayName, report, Core.version)).toInt() - } + val report: ByteArray = + fd?.use { Core.createReport(it.detachFd(), displayName) } + ?: Core.createReport(uri) + + lastInsertedId = + insertReport(Report(0, displayName, report, Core.version)).toInt() } // Don't go to report view when opening multiple files diff --git a/Source/GUI/Android/app/src/main/res/color/listitemcardview_selector.xml b/Source/GUI/Android/app/src/main/res/color/listitemcardview_selector.xml new file mode 100644 index 000000000..be2d9154e --- /dev/null +++ b/Source/GUI/Android/app/src/main/res/color/listitemcardview_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Source/GUI/Android/app/src/main/res/layout/activity_about.xml b/Source/GUI/Android/app/src/main/res/layout/activity_about.xml index 343caf8e2..077a7323d 100644 --- a/Source/GUI/Android/app/src/main/res/layout/activity_about.xml +++ b/Source/GUI/Android/app/src/main/res/layout/activity_about.xml @@ -12,15 +12,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" tools:context=".AboutActivity"> + app:liftOnScroll="true"> - - + \ No newline at end of file diff --git a/Source/GUI/Android/app/src/main/res/layout/activity_report_list.xml b/Source/GUI/Android/app/src/main/res/layout/activity_report_list.xml index 6f7ca3da8..380388d3f 100644 --- a/Source/GUI/Android/app/src/main/res/layout/activity_report_list.xml +++ b/Source/GUI/Android/app/src/main/res/layout/activity_report_list.xml @@ -28,6 +28,7 @@ - + android:text="@string/open_title" + app:icon="@drawable/ic_action_add" /> \ No newline at end of file diff --git a/Source/GUI/Android/app/src/main/res/layout/activity_settings.xml b/Source/GUI/Android/app/src/main/res/layout/activity_settings.xml index b6280e095..2d35ea808 100644 --- a/Source/GUI/Android/app/src/main/res/layout/activity_settings.xml +++ b/Source/GUI/Android/app/src/main/res/layout/activity_settings.xml @@ -15,10 +15,10 @@ tools:context=".SettingsActivity"> + android:id="@+id/app_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:liftOnScroll="true"> + android:layout_height="wrap_content" + app:liftOnScroll="true"> - - + \ No newline at end of file diff --git a/Source/GUI/Android/app/src/main/res/layout/clear_button.xml b/Source/GUI/Android/app/src/main/res/layout/clear_button.xml index 4b1f83389..3bd3b5503 100644 --- a/Source/GUI/Android/app/src/main/res/layout/clear_button.xml +++ b/Source/GUI/Android/app/src/main/res/layout/clear_button.xml @@ -1,10 +1,31 @@ -