Merge branch 'develop'
This commit is contained in:
commit
a396635639
161 changed files with 2024 additions and 962 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
## <a id="v1.11.4"></a>[v1.11.4] - 2024-07-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Collection: stack RAW and JPEG with same file names
|
||||||
|
- Collection: ask to rename/replace/skip when converting items with name conflict
|
||||||
|
- Export: bulk converting motion photos to still images
|
||||||
|
- Explorer: view folder tree and filter paths
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- switching to PiP when changing device orientation on Android >=13
|
||||||
|
- handling wallpaper intent without URI
|
||||||
|
- sizing widgets with some launchers on Android >=12
|
||||||
|
|
||||||
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
|
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -196,9 +196,9 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
implementation "androidx.appcompat:appcompat:1.7.0"
|
||||||
implementation 'androidx.core:core-ktx:1.13.1'
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-process:2.8.0'
|
implementation 'androidx.lifecycle:lifecycle-process:2.8.2'
|
||||||
implementation 'androidx.media:media:1.7.0'
|
implementation 'androidx.media:media:1.7.0'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:showWhenLocked="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
<activity
|
||||||
|
@ -143,6 +144,7 @@
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.provider.action.REVIEW" />
|
<action android:name="android.provider.action.REVIEW" />
|
||||||
|
<action android:name="android.provider.action.REVIEW_SECURE" />
|
||||||
<action android:name="com.android.camera.action.REVIEW" />
|
<action android:name="com.android.camera.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
||||||
|
|
||||||
|
@ -163,6 +165,7 @@
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.provider.action.REVIEW" />
|
<action android:name="android.provider.action.REVIEW" />
|
||||||
|
<action android:name="android.provider.action.REVIEW_SECURE" />
|
||||||
<action android:name="com.android.camera.action.REVIEW" />
|
<action android:name="com.android.camera.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@ import deckers.thibault.aves.utils.LogUtils
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
@ -34,13 +38,17 @@ import kotlin.coroutines.resumeWithException
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class AnalysisWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
|
class AnalysisWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
|
||||||
|
private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
private var workCont: Continuation<Any?>? = null
|
private var workCont: Continuation<Any?>? = null
|
||||||
private var flutterEngine: FlutterEngine? = null
|
private var flutterEngine: FlutterEngine? = null
|
||||||
private var backgroundChannel: MethodChannel? = null
|
private var backgroundChannel: MethodChannel? = null
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
createNotificationChannel()
|
defaultScope.launch {
|
||||||
setForeground(createForegroundInfo())
|
// prevent ANR triggered by slow operations in main thread
|
||||||
|
createNotificationChannel()
|
||||||
|
setForeground(createForegroundInfo())
|
||||||
|
}
|
||||||
suspendCoroutine { cont ->
|
suspendCoroutine { cont ->
|
||||||
workCont = cont
|
workCont = cont
|
||||||
onStart()
|
onStart()
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.SizeF
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import app.loup.streams_channel.StreamsChannel
|
import app.loup.streams_channel.StreamsChannel
|
||||||
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
||||||
|
@ -40,12 +41,16 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
for (widgetId in appWidgetIds) {
|
for (widgetId in appWidgetIds) {
|
||||||
val widgetInfo = appWidgetManager.getAppWidgetOptions(widgetId)
|
val widgetInfo = appWidgetManager.getAppWidgetOptions(widgetId)
|
||||||
|
|
||||||
defaultScope.launch {
|
goAsync().run {
|
||||||
val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
|
defaultScope.launch {
|
||||||
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundProps)
|
val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
|
||||||
|
updateWidgetImage(context, appWidgetManager, widgetId, backgroundProps)
|
||||||
|
|
||||||
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
|
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
|
||||||
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
|
updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
|
||||||
|
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,20 +66,32 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
imageByteFetchJob = defaultScope.launch {
|
imageByteFetchJob = defaultScope.launch {
|
||||||
delay(500)
|
delay(500)
|
||||||
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true)
|
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true)
|
||||||
updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
|
updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
|
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
|
||||||
|
|
||||||
private fun getWidgetSizePx(context: Context, widgetInfo: Bundle): Pair<Int, Int> {
|
private fun getWidgetSizesDip(context: Context, widgetInfo: Bundle): List<FieldMap> {
|
||||||
val devicePixelRatio = getDevicePixelRatio()
|
var sizes: List<SizeF>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF::class.java)
|
||||||
val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
|
@Suppress("DEPRECATION")
|
||||||
val widthPx = (widgetInfo.getInt(widthKey) * devicePixelRatio).roundToInt()
|
widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES)
|
||||||
val heightPx = (widgetInfo.getInt(heightKey) * devicePixelRatio).roundToInt()
|
} else {
|
||||||
return Pair(widthPx, heightPx)
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizes.isNullOrEmpty()) {
|
||||||
|
val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||||
|
val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
|
||||||
|
val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
|
||||||
|
val widthDip = widgetInfo.getInt(widthKey)
|
||||||
|
val heightDip = widgetInfo.getInt(heightKey)
|
||||||
|
sizes = listOf(SizeF(widthDip.toFloat(), heightDip.toFloat()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizes.map { size -> hashMapOf("widthDip" to size.width, "heightDip" to size.height) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getProps(
|
private suspend fun getProps(
|
||||||
|
@ -84,8 +101,11 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
drawEntryImage: Boolean,
|
drawEntryImage: Boolean,
|
||||||
reuseEntry: Boolean = false,
|
reuseEntry: Boolean = false,
|
||||||
): FieldMap? {
|
): FieldMap? {
|
||||||
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
|
val sizesDip = getWidgetSizesDip(context, widgetInfo)
|
||||||
if (widthPx == 0 || heightPx == 0) return null
|
if (sizesDip.isEmpty()) return null
|
||||||
|
|
||||||
|
val sizeDip = sizesDip.first()
|
||||||
|
if (sizeDip["widthDip"] == 0 || sizeDip["heightDip"] == 0) return null
|
||||||
|
|
||||||
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
|
@ -98,13 +118,16 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
FlutterUtils.runOnUiThread {
|
FlutterUtils.runOnUiThread {
|
||||||
channel.invokeMethod("drawWidget", hashMapOf(
|
channel.invokeMethod("drawWidget", hashMapOf(
|
||||||
"widgetId" to widgetId,
|
"widgetId" to widgetId,
|
||||||
"widthPx" to widthPx,
|
"sizesDip" to sizesDip,
|
||||||
"heightPx" to heightPx,
|
|
||||||
"devicePixelRatio" to getDevicePixelRatio(),
|
"devicePixelRatio" to getDevicePixelRatio(),
|
||||||
"drawEntryImage" to drawEntryImage,
|
"drawEntryImage" to drawEntryImage,
|
||||||
"reuseEntry" to reuseEntry,
|
"reuseEntry" to reuseEntry,
|
||||||
"isSystemThemeDark" to isNightModeOn,
|
"isSystemThemeDark" to isNightModeOn,
|
||||||
), object : MethodChannel.Result {
|
).apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
|
||||||
|
}
|
||||||
|
}, object : MethodChannel.Result {
|
||||||
override fun success(result: Any?) {
|
override fun success(result: Any?) {
|
||||||
cont.resume(result)
|
cont.resume(result)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +146,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
return props as FieldMap?
|
return props as FieldMap?
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId widthPx=$widthPx heightPx=$heightPx", e)
|
Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId sizesPx=$sizesDip", e)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -132,36 +155,83 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
widgetInfo: Bundle,
|
|
||||||
props: FieldMap?,
|
props: FieldMap?,
|
||||||
) {
|
) {
|
||||||
props ?: return
|
props ?: return
|
||||||
|
|
||||||
val bytes = props["bytes"] as ByteArray?
|
val bytesBySizeDip = (props["bytesBySizeDip"] as List<*>?)?.mapNotNull {
|
||||||
|
if (it is Map<*, *>) {
|
||||||
|
val widthDip = (it["widthDip"] as Number?)?.toFloat()
|
||||||
|
val heightDip = (it["heightDip"] as Number?)?.toFloat()
|
||||||
|
val bytes = it["bytes"] as ByteArray?
|
||||||
|
if (widthDip != null && heightDip != null && bytes != null) {
|
||||||
|
Pair(SizeF(widthDip, heightDip), bytes)
|
||||||
|
} else null
|
||||||
|
} else null
|
||||||
|
}
|
||||||
val updateOnTap = props["updateOnTap"] as Boolean?
|
val updateOnTap = props["updateOnTap"] as Boolean?
|
||||||
if (bytes == null || updateOnTap == null) {
|
if (bytesBySizeDip == null || updateOnTap == null) {
|
||||||
Log.e(LOG_TAG, "missing arguments")
|
Log.e(LOG_TAG, "missing arguments")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
|
if (bytesBySizeDip.isEmpty()) {
|
||||||
if (widthPx == 0 || heightPx == 0) return
|
Log.e(LOG_TAG, "empty image list")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmaps = ArrayList<Bitmap>()
|
||||||
|
|
||||||
|
fun createRemoteViewsForSize(
|
||||||
|
context: Context,
|
||||||
|
widgetId: Int,
|
||||||
|
sizeDip: SizeF,
|
||||||
|
bytes: ByteArray,
|
||||||
|
updateOnTap: Boolean,
|
||||||
|
): RemoteViews? {
|
||||||
|
val devicePixelRatio = getDevicePixelRatio()
|
||||||
|
val widthPx = (sizeDip.width * devicePixelRatio).roundToInt()
|
||||||
|
val heightPx = (sizeDip.height * devicePixelRatio).roundToInt()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888).also {
|
||||||
|
bitmaps.add(it)
|
||||||
|
it.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
|
||||||
|
|
||||||
|
return RemoteViews(context.packageName, R.layout.app_widget).apply {
|
||||||
|
setImageViewBitmap(R.id.widget_img, bitmap)
|
||||||
|
setOnClickPendingIntent(R.id.widget_img, pendingIntent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "failed to draw widget", e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
|
// multiple rendering for all possible sizes
|
||||||
|
val views = RemoteViews(
|
||||||
val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
|
bytesBySizeDip.associateBy(
|
||||||
|
{ (sizeDip, _) -> sizeDip },
|
||||||
val views = RemoteViews(context.packageName, R.layout.app_widget).apply {
|
{ (sizeDip, bytes) -> createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap) },
|
||||||
setImageViewBitmap(R.id.widget_img, bitmap)
|
).filterValues { it != null }.mapValues { (_, view) -> view!! }
|
||||||
setOnClickPendingIntent(R.id.widget_img, pendingIntent)
|
)
|
||||||
|
appWidgetManager.updateAppWidget(widgetId, views)
|
||||||
|
} else {
|
||||||
|
// single rendering
|
||||||
|
val (sizeDip, bytes) = bytesBySizeDip.first()
|
||||||
|
val views = createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap)
|
||||||
|
appWidgetManager.updateAppWidget(widgetId, views)
|
||||||
}
|
}
|
||||||
|
|
||||||
appWidgetManager.updateAppWidget(widgetId, views)
|
|
||||||
bitmap.recycle()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "failed to draw widget", e)
|
Log.e(LOG_TAG, "failed to draw widget", e)
|
||||||
|
} finally {
|
||||||
|
bitmaps.forEach { it.recycle() }
|
||||||
|
bitmaps.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.os.TransactionTooLargeException
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
@ -21,6 +22,7 @@ import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
||||||
import deckers.thibault.aves.channel.calls.AccessibilityHandler
|
import deckers.thibault.aves.channel.calls.AccessibilityHandler
|
||||||
import deckers.thibault.aves.channel.calls.AnalysisHandler
|
import deckers.thibault.aves.channel.calls.AnalysisHandler
|
||||||
import deckers.thibault.aves.channel.calls.AppAdapterHandler
|
import deckers.thibault.aves.channel.calls.AppAdapterHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.channel.calls.DebugHandler
|
import deckers.thibault.aves.channel.calls.DebugHandler
|
||||||
import deckers.thibault.aves.channel.calls.DeviceHandler
|
import deckers.thibault.aves.channel.calls.DeviceHandler
|
||||||
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
|
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
|
||||||
|
@ -36,6 +38,7 @@ import deckers.thibault.aves.channel.calls.MetadataEditHandler
|
||||||
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
|
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
|
||||||
import deckers.thibault.aves.channel.calls.SecurityHandler
|
import deckers.thibault.aves.channel.calls.SecurityHandler
|
||||||
import deckers.thibault.aves.channel.calls.StorageHandler
|
import deckers.thibault.aves.channel.calls.StorageHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.WallpaperHandler
|
||||||
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
|
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
|
||||||
import deckers.thibault.aves.channel.calls.window.WindowHandler
|
import deckers.thibault.aves.channel.calls.window.WindowHandler
|
||||||
import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler
|
import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler
|
||||||
|
@ -135,6 +138,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
|
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
|
||||||
MethodChannel(messenger, MediaEditHandler.CHANNEL).setMethodCallHandler(MediaEditHandler(this))
|
MethodChannel(messenger, MediaEditHandler.CHANNEL).setMethodCallHandler(MediaEditHandler(this))
|
||||||
MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this))
|
MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this))
|
||||||
|
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
|
||||||
// - need Activity
|
// - need Activity
|
||||||
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
|
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
|
||||||
|
|
||||||
|
@ -168,7 +172,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
intentDataMap.clear()
|
intentDataMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
"submitPickedItems" -> submitPickedItems(call)
|
"submitPickedItems" -> safe(call, result, ::submitPickedItems)
|
||||||
"submitPickedCollectionFilters" -> submitPickedCollectionFilters(call)
|
"submitPickedCollectionFilters" -> submitPickedCollectionFilters(call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,16 +305,32 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
Intent.ACTION_VIEW,
|
Intent.ACTION_VIEW,
|
||||||
Intent.ACTION_SEND,
|
Intent.ACTION_SEND,
|
||||||
MediaStore.ACTION_REVIEW,
|
MediaStore.ACTION_REVIEW,
|
||||||
|
MediaStore.ACTION_REVIEW_SECURE,
|
||||||
"com.android.camera.action.REVIEW",
|
"com.android.camera.action.REVIEW",
|
||||||
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
|
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
|
||||||
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
||||||
// MIME type is optional
|
// MIME type is optional
|
||||||
val type = intent.type ?: intent.resolveType(this)
|
val type = intent.type ?: intent.resolveType(this)
|
||||||
return hashMapOf(
|
val fields = hashMapOf<String, Any?>(
|
||||||
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW,
|
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW,
|
||||||
INTENT_DATA_KEY_MIME_TYPE to type,
|
INTENT_DATA_KEY_MIME_TYPE to type,
|
||||||
INTENT_DATA_KEY_URI to uri.toString(),
|
INTENT_DATA_KEY_URI to uri.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (action == MediaStore.ACTION_REVIEW_SECURE) {
|
||||||
|
val uris = ArrayList<String>()
|
||||||
|
intent.clipData?.let { clipData ->
|
||||||
|
for (i in 0 until clipData.itemCount) {
|
||||||
|
clipData.getItemAt(i).uri?.let { uris.add(it.toString()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields[INTENT_DATA_KEY_SECURE_URIS] = uris
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && intent.hasExtra(MediaStore.EXTRA_BRIGHTNESS)) {
|
||||||
|
fields[INTENT_DATA_KEY_BRIGHTNESS] = intent.getFloatExtra(MediaStore.EXTRA_BRIGHTNESS, 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,28 +410,36 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submitPickedItems(call: MethodCall) {
|
open fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val pickedUris = call.argument<List<String>>("uris")
|
val pickedUris = call.argument<List<String>>("uris")
|
||||||
if (!pickedUris.isNullOrEmpty()) {
|
try {
|
||||||
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
|
if (!pickedUris.isNullOrEmpty()) {
|
||||||
val intent = Intent().apply {
|
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
|
||||||
val firstUri = toUri(pickedUris.first())
|
val intent = Intent().apply {
|
||||||
if (pickedUris.size == 1) {
|
val firstUri = toUri(pickedUris.first())
|
||||||
data = firstUri
|
if (pickedUris.size == 1) {
|
||||||
} else {
|
data = firstUri
|
||||||
clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
|
} else {
|
||||||
pickedUris.drop(1).forEach {
|
clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
|
||||||
addItem(ClipData.Item(toUri(it)))
|
pickedUris.drop(1).forEach {
|
||||||
|
addItem(ClipData.Item(toUri(it)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
setResult(RESULT_OK, intent)
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) {
|
||||||
|
result.error("submitPickedItems-large", "transaction too large with ${pickedUris?.size} URIs", e)
|
||||||
|
} else {
|
||||||
|
result.error("submitPickedItems-exception", "failed to pick ${pickedUris?.size} URIs", e)
|
||||||
}
|
}
|
||||||
setResult(RESULT_OK, intent)
|
|
||||||
} else {
|
|
||||||
setResult(RESULT_CANCELED)
|
|
||||||
}
|
}
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submitPickedCollectionFilters(call: MethodCall) {
|
private fun submitPickedCollectionFilters(call: MethodCall) {
|
||||||
|
@ -498,11 +526,13 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
|
|
||||||
const val INTENT_DATA_KEY_ACTION = "action"
|
const val INTENT_DATA_KEY_ACTION = "action"
|
||||||
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
|
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
|
||||||
|
const val INTENT_DATA_KEY_BRIGHTNESS = "brightness"
|
||||||
const val INTENT_DATA_KEY_FILTERS = "filters"
|
const val INTENT_DATA_KEY_FILTERS = "filters"
|
||||||
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
|
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
|
||||||
const val INTENT_DATA_KEY_PAGE = "page"
|
const val INTENT_DATA_KEY_PAGE = "page"
|
||||||
const val INTENT_DATA_KEY_QUERY = "query"
|
const val INTENT_DATA_KEY_QUERY = "query"
|
||||||
const val INTENT_DATA_KEY_SAFE_MODE = "safeMode"
|
const val INTENT_DATA_KEY_SAFE_MODE = "safeMode"
|
||||||
|
const val INTENT_DATA_KEY_SECURE_URIS = "secureUris"
|
||||||
const val INTENT_DATA_KEY_URI = "uri"
|
const val INTENT_DATA_KEY_URI = "uri"
|
||||||
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
|
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
|
||||||
|
|
||||||
|
|
|
@ -2,132 +2,54 @@ package deckers.thibault.aves
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import deckers.thibault.aves.channel.calls.AppAdapterHandler
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
|
||||||
import app.loup.streams_channel.StreamsChannel
|
|
||||||
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
|
||||||
import deckers.thibault.aves.channel.calls.AccessibilityHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.DeviceHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.MediaSessionHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.StorageHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.WallpaperHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
|
|
||||||
import deckers.thibault.aves.channel.calls.window.WindowHandler
|
|
||||||
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
|
|
||||||
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
|
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
|
||||||
import deckers.thibault.aves.utils.getParcelableExtraCompat
|
import deckers.thibault.aves.utils.getParcelableExtraCompat
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
|
||||||
import io.flutter.plugin.common.EventChannel
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class WallpaperActivity : FlutterFragmentActivity() {
|
class WallpaperActivity : MainActivity() {
|
||||||
private lateinit var intentDataMap: FieldMap
|
private var originalIntent: String? = null
|
||||||
private lateinit var mediaSessionHandler: MediaSessionHandler
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun extractIntentData(intent: Intent?): FieldMap {
|
||||||
super.onCreate(savedInstanceState)
|
if (intent != null) {
|
||||||
|
when (intent.action) {
|
||||||
|
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
||||||
|
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
||||||
|
// MIME type is optional
|
||||||
|
val type = intent.type ?: intent.resolveType(this)
|
||||||
|
return hashMapOf(
|
||||||
|
INTENT_DATA_KEY_ACTION to INTENT_ACTION_SET_WALLPAPER,
|
||||||
|
INTENT_DATA_KEY_MIME_TYPE to type,
|
||||||
|
INTENT_DATA_KEY_URI to uri.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Log.i(LOG_TAG, "onCreate intent=$intent")
|
// if the media URI is not provided we need to pick one first
|
||||||
intent.extras?.takeUnless { it.isEmpty }?.let {
|
originalIntent = intent.action
|
||||||
Log.i(LOG_TAG, "onCreate intent extras=$it")
|
intent.action = Intent.ACTION_PICK
|
||||||
}
|
|
||||||
intentDataMap = extractIntentData(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
|
||||||
super.configureFlutterEngine(flutterEngine)
|
|
||||||
val messenger = flutterEngine.dartExecutor
|
|
||||||
|
|
||||||
// notification: platform -> dart
|
|
||||||
val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
|
|
||||||
EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart -> platform -> dart
|
|
||||||
// - need Context
|
|
||||||
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
|
|
||||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
|
|
||||||
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
|
|
||||||
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
|
|
||||||
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
|
|
||||||
MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
|
|
||||||
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
|
||||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
|
|
||||||
// - need ContextWrapper
|
|
||||||
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
|
|
||||||
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
|
|
||||||
// - need Activity
|
|
||||||
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
|
|
||||||
|
|
||||||
// result streaming: dart -> platform ->->-> dart
|
|
||||||
// - need Context
|
|
||||||
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
|
|
||||||
|
|
||||||
// intent handling
|
|
||||||
// detail fetch: dart -> platform
|
|
||||||
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> onMethodCall(call, result) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
Log.i(LOG_TAG, "onStart")
|
|
||||||
super.onStart()
|
|
||||||
|
|
||||||
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
|
|
||||||
// are incorrect on startup in some environments (e.g. API 29 emulator),
|
|
||||||
// so we manually request to apply the insets to update the window metrics
|
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
|
||||||
window.decorView.requestApplyInsets()
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
mediaSessionHandler.dispose()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
when (call.method) {
|
|
||||||
"getIntentData" -> {
|
|
||||||
result.success(intentDataMap)
|
|
||||||
intentDataMap.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractIntentData(intent: Intent?): FieldMap {
|
|
||||||
when (intent?.action) {
|
|
||||||
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
|
||||||
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
|
||||||
// MIME type is optional
|
|
||||||
val type = intent.type ?: intent.resolveType(this)
|
|
||||||
return hashMapOf(
|
|
||||||
MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER,
|
|
||||||
MainActivity.INTENT_DATA_KEY_MIME_TYPE to type,
|
|
||||||
MainActivity.INTENT_DATA_KEY_URI to uri.toString(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Intent.ACTION_RUN -> {
|
|
||||||
// flutter run
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Log.w(LOG_TAG, "unhandled intent action=${intent?.action}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return HashMap()
|
|
||||||
|
return super.extractIntentData(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
|
||||||
private val LOG_TAG = LogUtils.createTag<WallpaperActivity>()
|
if (originalIntent != null) {
|
||||||
|
val pickedUris = call.argument<List<String>>("uris")
|
||||||
|
if (!pickedUris.isNullOrEmpty()) {
|
||||||
|
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
|
||||||
|
onNewIntent(Intent().apply {
|
||||||
|
action = originalIntent
|
||||||
|
data = toUri(pickedUris.first())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.submitPickedItems(call, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
||||||
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
|
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
|
||||||
"deleteEmptyDirectories" -> ioScope.launch { safe(call, result, ::deleteEmptyDirectories) }
|
"deleteEmptyDirectories" -> ioScope.launch { safe(call, result, ::deleteEmptyDirectories) }
|
||||||
"deleteTempDirectory" -> ioScope.launch { safe(call, result, ::deleteTempDirectory) }
|
"deleteTempDirectory" -> ioScope.launch { safe(call, result, ::deleteTempDirectory) }
|
||||||
|
"deleteExternalCache" -> ioScope.launch { safe(call, result, ::deleteExternalCache) }
|
||||||
"canRequestMediaFileBulkAccess" -> safe(call, result, ::canRequestMediaFileBulkAccess)
|
"canRequestMediaFileBulkAccess" -> safe(call, result, ::canRequestMediaFileBulkAccess)
|
||||||
"canInsertMedia" -> safe(call, result, ::canInsertMedia)
|
"canInsertMedia" -> safe(call, result, ::canInsertMedia)
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
|
@ -49,16 +50,17 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
||||||
var internalCache = getFolderSize(context.cacheDir)
|
var internalCache = getFolderSize(context.cacheDir)
|
||||||
internalCache += getFolderSize(context.codeCacheDir)
|
internalCache += getFolderSize(context.codeCacheDir)
|
||||||
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
|
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
|
||||||
|
val externalFilesDirs = context.getExternalFilesDirs(null)
|
||||||
|
|
||||||
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
|
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
|
||||||
|
|
||||||
val database = getFolderSize(File(dataDir, "databases"))
|
val database = getFolderSize(File(dataDir, "databases"))
|
||||||
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
|
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
|
||||||
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
|
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
|
||||||
val trash = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
|
val trash = externalFilesDirs.mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
|
||||||
|
|
||||||
val internalData = getFolderSize(dataDir) - internalCache
|
val internalData = getFolderSize(dataDir) - internalCache
|
||||||
val externalData = context.getExternalFilesDirs(null).map(::getFolderSize).sum()
|
val externalData = externalFilesDirs.map(::getFolderSize).sum()
|
||||||
val miscData = internalData + externalData - (database + flutter + vaults + trash)
|
val miscData = internalData + externalData - (database + flutter + vaults + trash)
|
||||||
|
|
||||||
result.success(
|
result.success(
|
||||||
|
@ -224,6 +226,11 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(StorageUtils.deleteTempDirectory(context))
|
result.success(StorageUtils.deleteTempDirectory(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteExternalCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
context.externalCacheDirs.filter { it.exists() }.forEach { it.deleteRecursively() }
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
private fun canRequestMediaFileBulkAccess(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
private fun canRequestMediaFileBulkAccess(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
|
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package deckers.thibault.aves.model
|
package deckers.thibault.aves.model
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
enum class NameConflictStrategy {
|
enum class NameConflictStrategy {
|
||||||
RENAME, REPLACE, SKIP;
|
RENAME, REPLACE, SKIP;
|
||||||
|
|
||||||
|
@ -10,3 +12,5 @@ enum class NameConflictStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NameConflictResolution(var nameWithoutExtension: String?, var replacementFile: File?)
|
|
@ -41,6 +41,7 @@ import deckers.thibault.aves.metadata.xmp.GoogleXMP
|
||||||
import deckers.thibault.aves.model.AvesEntry
|
import deckers.thibault.aves.model.AvesEntry
|
||||||
import deckers.thibault.aves.model.ExifOrientationOp
|
import deckers.thibault.aves.model.ExifOrientationOp
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
|
import deckers.thibault.aves.model.NameConflictResolution
|
||||||
import deckers.thibault.aves.model.NameConflictStrategy
|
import deckers.thibault.aves.model.NameConflictStrategy
|
||||||
import deckers.thibault.aves.model.SourceEntry
|
import deckers.thibault.aves.model.SourceEntry
|
||||||
import deckers.thibault.aves.utils.BitmapUtils
|
import deckers.thibault.aves.utils.BitmapUtils
|
||||||
|
@ -147,13 +148,14 @@ abstract class ImageProvider {
|
||||||
val oldFile = File(sourcePath)
|
val oldFile = File(sourcePath)
|
||||||
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
|
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
|
||||||
oldFile.parent?.let { dir ->
|
oldFile.parent?.let { dir ->
|
||||||
resolveTargetFileNameWithoutExtension(
|
val resolution = resolveTargetFileNameWithoutExtension(
|
||||||
contextWrapper = activity,
|
contextWrapper = activity,
|
||||||
dir = dir,
|
dir = dir,
|
||||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
conflictStrategy = NameConflictStrategy.RENAME,
|
conflictStrategy = NameConflictStrategy.RENAME,
|
||||||
)?.let { targetNameWithoutExtension ->
|
)
|
||||||
|
resolution.nameWithoutExtension?.let { targetNameWithoutExtension ->
|
||||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
||||||
val newFile = File(dir, targetFileName)
|
val newFile = File(dir, targetFileName)
|
||||||
if (oldFile != newFile) {
|
if (oldFile != newFile) {
|
||||||
|
@ -266,7 +268,7 @@ abstract class ImageProvider {
|
||||||
exportMimeType: String,
|
exportMimeType: String,
|
||||||
): FieldMap {
|
): FieldMap {
|
||||||
val sourceMimeType = sourceEntry.mimeType
|
val sourceMimeType = sourceEntry.mimeType
|
||||||
val sourceUri = sourceEntry.uri
|
var sourceUri = sourceEntry.uri
|
||||||
val pageId = sourceEntry.pageId
|
val pageId = sourceEntry.pageId
|
||||||
|
|
||||||
var desiredNameWithoutExtension = if (sourceEntry.path != null) {
|
var desiredNameWithoutExtension = if (sourceEntry.path != null) {
|
||||||
|
@ -279,13 +281,17 @@ abstract class ImageProvider {
|
||||||
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
|
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
|
||||||
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
|
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
|
||||||
}
|
}
|
||||||
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
|
val resolution = resolveTargetFileNameWithoutExtension(
|
||||||
contextWrapper = activity,
|
contextWrapper = activity,
|
||||||
dir = targetDir,
|
dir = targetDir,
|
||||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||||
mimeType = exportMimeType,
|
mimeType = exportMimeType,
|
||||||
conflictStrategy = nameConflictStrategy,
|
conflictStrategy = nameConflictStrategy,
|
||||||
) ?: return skippedFieldMap
|
)
|
||||||
|
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
|
||||||
|
resolution.replacementFile?.let { file ->
|
||||||
|
sourceUri = Uri.fromFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
val targetMimeType: String
|
val targetMimeType: String
|
||||||
val write: (OutputStream) -> Unit
|
val write: (OutputStream) -> Unit
|
||||||
|
@ -391,6 +397,8 @@ abstract class ImageProvider {
|
||||||
} finally {
|
} finally {
|
||||||
// clearing Glide target should happen after effectively writing the bitmap
|
// clearing Glide target should happen after effectively writing the bitmap
|
||||||
Glide.with(activity).clear(target)
|
Glide.with(activity).clear(target)
|
||||||
|
|
||||||
|
resolution.replacementFile?.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +478,7 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
val captureMimeType = MimeTypes.JPEG
|
val captureMimeType = MimeTypes.JPEG
|
||||||
val targetNameWithoutExtension = try {
|
val resolution = try {
|
||||||
resolveTargetFileNameWithoutExtension(
|
resolveTargetFileNameWithoutExtension(
|
||||||
contextWrapper = contextWrapper,
|
contextWrapper = contextWrapper,
|
||||||
dir = targetDir,
|
dir = targetDir,
|
||||||
|
@ -483,6 +491,7 @@ abstract class ImageProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val targetNameWithoutExtension = resolution.nameWithoutExtension
|
||||||
if (targetNameWithoutExtension == null) {
|
if (targetNameWithoutExtension == null) {
|
||||||
// skip it
|
// skip it
|
||||||
callback.onSuccess(skippedFieldMap)
|
callback.onSuccess(skippedFieldMap)
|
||||||
|
@ -568,10 +577,13 @@ abstract class ImageProvider {
|
||||||
desiredNameWithoutExtension: String,
|
desiredNameWithoutExtension: String,
|
||||||
mimeType: String,
|
mimeType: String,
|
||||||
conflictStrategy: NameConflictStrategy,
|
conflictStrategy: NameConflictStrategy,
|
||||||
): String? {
|
): NameConflictResolution {
|
||||||
|
var resolvedName: String? = desiredNameWithoutExtension
|
||||||
|
var replacementFile: File? = null
|
||||||
|
|
||||||
val extension = extensionFor(mimeType)
|
val extension = extensionFor(mimeType)
|
||||||
val targetFile = File(dir, "$desiredNameWithoutExtension$extension")
|
val targetFile = File(dir, "$desiredNameWithoutExtension$extension")
|
||||||
return when (conflictStrategy) {
|
when (conflictStrategy) {
|
||||||
NameConflictStrategy.RENAME -> {
|
NameConflictStrategy.RENAME -> {
|
||||||
var nameWithoutExtension = desiredNameWithoutExtension
|
var nameWithoutExtension = desiredNameWithoutExtension
|
||||||
var i = 0
|
var i = 0
|
||||||
|
@ -579,24 +591,28 @@ abstract class ImageProvider {
|
||||||
i++
|
i++
|
||||||
nameWithoutExtension = "$desiredNameWithoutExtension ($i)"
|
nameWithoutExtension = "$desiredNameWithoutExtension ($i)"
|
||||||
}
|
}
|
||||||
nameWithoutExtension
|
resolvedName = nameWithoutExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
NameConflictStrategy.REPLACE -> {
|
NameConflictStrategy.REPLACE -> {
|
||||||
if (targetFile.exists()) {
|
if (targetFile.exists()) {
|
||||||
|
// move replaced file to temp storage
|
||||||
|
// so that it can be used as a source for conversion or metadata copy
|
||||||
|
replacementFile = StorageUtils.createTempFile(contextWrapper).apply {
|
||||||
|
targetFile.transferTo(outputStream())
|
||||||
|
}
|
||||||
deletePath(contextWrapper, targetFile.path, mimeType)
|
deletePath(contextWrapper, targetFile.path, mimeType)
|
||||||
}
|
}
|
||||||
desiredNameWithoutExtension
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NameConflictStrategy.SKIP -> {
|
NameConflictStrategy.SKIP -> {
|
||||||
if (targetFile.exists()) {
|
if (targetFile.exists()) {
|
||||||
null
|
resolvedName = null
|
||||||
} else {
|
|
||||||
desiredNameWithoutExtension
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NameConflictResolution(resolvedName, replacementFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
|
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
|
||||||
|
|
|
@ -562,13 +562,14 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
|
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
|
||||||
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
|
val resolution = resolveTargetFileNameWithoutExtension(
|
||||||
contextWrapper = activity,
|
contextWrapper = activity,
|
||||||
dir = targetDir,
|
dir = targetDir,
|
||||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
conflictStrategy = nameConflictStrategy,
|
conflictStrategy = nameConflictStrategy,
|
||||||
) ?: return skippedFieldMap
|
)
|
||||||
|
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
|
||||||
|
|
||||||
val sourceDocFile = DocumentFileCompat.fromSingleUri(activity, sourceUri)
|
val sourceDocFile = DocumentFileCompat.fromSingleUri(activity, sourceUri)
|
||||||
val targetPath = createSingle(
|
val targetPath = createSingle(
|
||||||
|
|
|
@ -6,32 +6,32 @@
|
||||||
<path
|
<path
|
||||||
android:fillColor="#ef435a"
|
android:fillColor="#ef435a"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m40.44,57.44 l8.74,8.74a1.54,1.54 0,0 1,0 2.18l-4.18,4.18a7.99,7.99 0,0 1,-11.3 0l-4.18,-4.18a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
|
android:pathData="m41.03,57.12 l8.31,8.31a1.47,1.47 0,0 1,0 2.08l-3.97,3.97a7.6,7.6 0,0 1,-10.75 0l-3.97,-3.97a1.47,1.47 0,0 1,0 -2.08l8.31,-8.31a1.47,1.47 0,0 1,2.08 0z"
|
||||||
android:strokeWidth="1.61863"
|
android:strokeWidth="1.53903"
|
||||||
android:strokeColor="#000000"
|
android:strokeColor="#000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#e0e0e0"
|
android:fillColor="#e0e0e0"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m53.48,44.4 l8.74,8.74a1.54,1.54 0,0 1,-0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-8.74,-8.74a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
|
android:pathData="m53.43,44.71 l8.31,8.31a1.46,1.46 0,0 1,-0 2.08l-8.31,8.31a1.46,1.46 0,0 1,-2.08 0l-8.31,-8.31a1.46,1.46 0,0 1,0 -2.08l8.31,-8.31a1.46,1.46 0,0 1,2.08 0z"
|
||||||
android:strokeWidth="1.61862"
|
android:strokeWidth="1.53902"
|
||||||
android:strokeColor="#000000"
|
android:strokeColor="#000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#ffc11f"
|
android:fillColor="#ffc11f"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m55.61,40.09a1.54,1.54 0,0 0,0 2.18l8.74,8.74a1.54,1.54 0,0 0,2.18 0l8.74,-8.74a1.54,1.54 0,0 0,0 -2.18l-4.34,-4.34a7.77,7.77 0,0 0,-10.98 0zM64.23,39.98a1.71,1.71 0,0 1,2.41 0,1.71 1.71,0 0,1 0,2.41 1.71,1.71 0,0 1,-2.41 0,1.71 1.71,0 0,1 0,-2.41z"
|
android:pathData="m55.45,40.62a1.47,1.47 0,0 0,0 2.08l8.31,8.31a1.47,1.47 0,0 0,2.08 0l8.31,-8.31a1.47,1.47 0,0 0,0 -2.08l-4.12,-4.12a7.39,7.39 0,0 0,-10.44 0zM63.65,40.51a1.62,1.62 0,0 1,2.29 0,1.62 1.62,0 0,1 0,2.29 1.62,1.62 0,0 1,-2.29 0,1.62 1.62,0 0,1 0,-2.29z"
|
||||||
android:strokeWidth="1.61862"
|
android:strokeWidth="1.53902"
|
||||||
android:strokeColor="#000000"
|
android:strokeColor="#000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/ic_launcher_flavour"
|
android:fillColor="@color/ic_launcher_flavour"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m36.47,27.39 l12.76,12.76a1.54,1.54 0,0 1,0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-5.67,-5.67a12.06,12.06 0,0 1,0 -17.06l0.95,-0.95a2.04,2.04 0,0 1,2.88 0z"
|
android:pathData="m37.26,28.54 l12.13,12.13a1.47,1.47 0,0 1,0 2.08l-8.31,8.31a1.47,1.47 0,0 1,-2.08 0l-5.39,-5.39a11.47,11.47 0,0 1,0 -16.22l0.9,-0.9a1.94,1.94 0,0 1,2.74 0z"
|
||||||
android:strokeWidth="1.61863"
|
android:strokeWidth="1.53903"
|
||||||
android:strokeColor="#000000"
|
android:strokeColor="#000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
|
|
|
@ -6,32 +6,32 @@
|
||||||
<path
|
<path
|
||||||
android:fillColor="#000000"
|
android:fillColor="#000000"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m40.44,57.44 l8.74,8.74a1.54,1.54 0,0 1,0 2.18l-4.18,4.18a7.99,7.99 0,0 1,-11.3 0l-4.18,-4.18a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
|
android:pathData="m41.03,57.12 l8.31,8.31a1.47,1.47 0,0 1,0 2.08l-3.97,3.97a7.6,7.6 0,0 1,-10.75 0l-3.97,-3.97a1.47,1.47 0,0 1,0 -2.08l8.3,-8.3a1.47,1.47 0,0 1,2.08 0z"
|
||||||
android:strokeWidth="1.61863"
|
android:strokeWidth="1.53871"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#000000"
|
android:fillColor="#000000"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m53.48,44.4 l8.74,8.74a1.54,1.54 0,0 1,-0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-8.74,-8.74a1.54,1.54 0,0 1,0 -2.18l8.74,-8.74a1.54,1.54 0,0 1,2.18 0z"
|
android:pathData="m53.43,44.72 l8.31,8.31a1.46,1.46 0,0 1,-0 2.08l-8.31,8.31a1.46,1.46 0,0 1,-2.08 0l-8.31,-8.31a1.46,1.46 0,0 1,0 -2.08L51.35,44.72a1.46,1.46 0,0 1,2.08 0z"
|
||||||
android:strokeWidth="1.61862"
|
android:strokeWidth="1.5387"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#000000"
|
android:fillColor="#000000"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m55.61,40.09a1.54,1.54 0,0 0,0 2.18l8.74,8.74a1.54,1.54 0,0 0,2.18 0l8.74,-8.74a1.54,1.54 0,0 0,0 -2.18l-4.34,-4.34a7.77,7.77 0,0 0,-10.98 0zM64.23,39.98a1.71,1.71 0,0 1,2.41 0,1.71 1.71,0 0,1 0,2.41 1.71,1.71 0,0 1,-2.41 0,1.71 1.71,0 0,1 0,-2.41z"
|
android:pathData="m55.45,40.62a1.47,1.47 0,0 0,0 2.08l8.31,8.3a1.47,1.47 0,0 0,2.08 0l8.3,-8.3a1.47,1.47 0,0 0,0 -2.08l-4.12,-4.12a7.38,7.38 0,0 0,-10.44 0zM63.65,40.51a1.62,1.62 0,0 1,2.29 0,1.62 1.62,0 0,1 0,2.29 1.62,1.62 0,0 1,-2.29 0,1.62 1.62,0 0,1 0,-2.29z"
|
||||||
android:strokeWidth="1.61862"
|
android:strokeWidth="1.5387"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="#000000"
|
android:fillColor="#000000"
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="m36.47,27.39 l12.76,12.76a1.54,1.54 0,0 1,0 2.18l-8.74,8.74a1.54,1.54 0,0 1,-2.18 0l-5.67,-5.67a12.06,12.06 0,0 1,0 -17.06l0.95,-0.95a2.04,2.04 0,0 1,2.88 0z"
|
android:pathData="m37.26,28.54 l12.13,12.13a1.47,1.47 0,0 1,0 2.08l-8.31,8.3a1.47,1.47 0,0 1,-2.08 0L33.62,45.67a11.47,11.47 0,0 1,0 -16.22l0.9,-0.9a1.94,1.94 0,0 1,2.74 0z"
|
||||||
android:strokeWidth="1.61863"
|
android:strokeWidth="1.53871"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeLineCap="round"
|
android:strokeLineCap="round"
|
||||||
android:strokeLineJoin="round" />
|
android:strokeLineJoin="round" />
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.6:
|
|
||||||
- detect HDR videos (but do not play them in their full HDR glory)
|
|
||||||
- removing animations also applies to pop up menus
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.6:
|
|
||||||
- detect HDR videos (but do not play them in their full HDR glory)
|
|
||||||
- removing animations also applies to pop up menus
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.7:
|
|
||||||
- detect HDR videos (but do not play them in their full HDR glory)
|
|
||||||
- removing animations also applies to pop up menus
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.7:
|
|
||||||
- detect HDR videos (but do not play them in their full HDR glory)
|
|
||||||
- removing animations also applies to pop up menus
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.8:
|
|
||||||
- rename in bulk using tags
|
|
||||||
- repeat a section section section of a video
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.8:
|
|
||||||
- rename in bulk using tags
|
|
||||||
- repeat a section section section of a video
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.9:
|
|
||||||
- rename in bulk using tags
|
|
||||||
- repeat a section section section of a video
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.10.9:
|
|
||||||
- rename in bulk using tags
|
|
||||||
- repeat a section section section of a video
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,3 +0,0 @@
|
||||||
In v1.11.0:
|
|
||||||
- watch videos with SRT subtitle files
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,3 +0,0 @@
|
||||||
In v1.11.0:
|
|
||||||
- watch videos with SRT subtitle files
|
|
||||||
Full changelog available on GitHub
|
|
4
fastlane/metadata/android/en-US/changelogs/123.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/123.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
In v1.11.4:
|
||||||
|
- explore your collection with the... explorer
|
||||||
|
- convert your motion photos to stills in bulk
|
||||||
|
Full changelog available on GitHub
|
4
fastlane/metadata/android/en-US/changelogs/12301.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/12301.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
In v1.11.4:
|
||||||
|
- explore your collection with the... explorer
|
||||||
|
- convert your motion photos to stills in bulk
|
||||||
|
Full changelog available on GitHub
|
|
@ -1195,7 +1195,7 @@
|
||||||
"@collectionActionAddShortcut": {},
|
"@collectionActionAddShortcut": {},
|
||||||
"settingsViewerShowMinimap": "إظهار الخريطة المصغرة",
|
"settingsViewerShowMinimap": "إظهار الخريطة المصغرة",
|
||||||
"@settingsViewerShowMinimap": {},
|
"@settingsViewerShowMinimap": {},
|
||||||
"settingsCollectionBurstPatternsTile": "أنماط الانفجار",
|
"settingsCollectionBurstPatternsTile": "أنماط الصور المتتابعة",
|
||||||
"@settingsCollectionBurstPatternsTile": {},
|
"@settingsCollectionBurstPatternsTile": {},
|
||||||
"viewerInfoLabelPath": "المسار",
|
"viewerInfoLabelPath": "المسار",
|
||||||
"@viewerInfoLabelPath": {},
|
"@viewerInfoLabelPath": {},
|
||||||
|
@ -1538,5 +1538,9 @@
|
||||||
"renameProcessorHash": "تجزئة",
|
"renameProcessorHash": "تجزئة",
|
||||||
"@renameProcessorHash": {},
|
"@renameProcessorHash": {},
|
||||||
"chipActionShowCollection": "عرض في المجموعة",
|
"chipActionShowCollection": "عرض في المجموعة",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "عرض في المستكشف",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "المستكشف",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"@welcomeTermsToggle": {},
|
"@welcomeTermsToggle": {},
|
||||||
"welcomeOptional": "Неабавязковыя",
|
"welcomeOptional": "Неабавязковыя",
|
||||||
"@welcomeOptional": {},
|
"@welcomeOptional": {},
|
||||||
"welcomeMessage": "Сардэчна запрашаем ў Aves",
|
"welcomeMessage": "Сардэчна запрашаем у Aves",
|
||||||
"@welcomeMessage": {},
|
"@welcomeMessage": {},
|
||||||
"itemCount": "{count, plural, =1{{count} элемент} other{{count} элементаў}}",
|
"itemCount": "{count, plural, =1{{count} элемент} other{{count} элементаў}}",
|
||||||
"@itemCount": {
|
"@itemCount": {
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"@saveTooltip": {},
|
"@saveTooltip": {},
|
||||||
"doNotAskAgain": "Больш не пытайся",
|
"doNotAskAgain": "Больш не пытайся",
|
||||||
"@doNotAskAgain": {},
|
"@doNotAskAgain": {},
|
||||||
"chipActionGoToCountryPage": "Паказаць ў Краінах",
|
"chipActionGoToCountryPage": "Паказаць у Краінах",
|
||||||
"@chipActionGoToCountryPage": {},
|
"@chipActionGoToCountryPage": {},
|
||||||
"chipActionFilterOut": "Адфільтраваць",
|
"chipActionFilterOut": "Адфільтраваць",
|
||||||
"@chipActionFilterOut": {},
|
"@chipActionFilterOut": {},
|
||||||
|
@ -56,27 +56,27 @@
|
||||||
"@sourceStateCataloguing": {},
|
"@sourceStateCataloguing": {},
|
||||||
"chipActionDelete": "Выдаліць",
|
"chipActionDelete": "Выдаліць",
|
||||||
"@chipActionDelete": {},
|
"@chipActionDelete": {},
|
||||||
"chipActionGoToAlbumPage": "Паказаць ў Альбомах",
|
"chipActionGoToAlbumPage": "Паказаць у Альбомах",
|
||||||
"@chipActionGoToAlbumPage": {},
|
"@chipActionGoToAlbumPage": {},
|
||||||
"chipActionHide": "Схаваць",
|
"chipActionHide": "Схаваць",
|
||||||
"@chipActionHide": {},
|
"@chipActionHide": {},
|
||||||
"chipActionCreateVault": "Стварыце сховішча",
|
"chipActionCreateVault": "Стварыце сховішча",
|
||||||
"@chipActionCreateVault": {},
|
"@chipActionCreateVault": {},
|
||||||
"chipActionGoToPlacePage": "Паказаць ў Лакацыях",
|
"chipActionGoToPlacePage": "Паказаць у Лакацыях",
|
||||||
"@chipActionGoToPlacePage": {},
|
"@chipActionGoToPlacePage": {},
|
||||||
"chipActionUnpin": "Адмацаваць зверху",
|
"chipActionUnpin": "Адмацаваць зверху",
|
||||||
"@chipActionUnpin": {},
|
"@chipActionUnpin": {},
|
||||||
"chipActionGoToTagPage": "Паказаць ў Тэгах",
|
"chipActionGoToTagPage": "Паказаць у Тэгах",
|
||||||
"@chipActionGoToTagPage": {},
|
"@chipActionGoToTagPage": {},
|
||||||
"chipActionLock": "Заблакаваць",
|
"chipActionLock": "Заблакаваць",
|
||||||
"@chipActionLock": {},
|
"@chipActionLock": {},
|
||||||
"chipActionSetCover": "Ўсталяваць вокладку",
|
"chipActionSetCover": "Усталяваць вокладку",
|
||||||
"@chipActionSetCover": {},
|
"@chipActionSetCover": {},
|
||||||
"chipActionRename": "Перайменаваць",
|
"chipActionRename": "Перайменаваць",
|
||||||
"@chipActionRename": {},
|
"@chipActionRename": {},
|
||||||
"chipActionConfigureVault": "Наладзіць сховішча",
|
"chipActionConfigureVault": "Наладзіць сховішча",
|
||||||
"@chipActionConfigureVault": {},
|
"@chipActionConfigureVault": {},
|
||||||
"entryActionCopyToClipboard": "Скапіяваць ў буфер абмену",
|
"entryActionCopyToClipboard": "Скапіяваць у буфер абмену",
|
||||||
"@entryActionCopyToClipboard": {},
|
"@entryActionCopyToClipboard": {},
|
||||||
"entryActionDelete": "Выдаліць",
|
"entryActionDelete": "Выдаліць",
|
||||||
"@entryActionDelete": {},
|
"@entryActionDelete": {},
|
||||||
|
@ -120,15 +120,15 @@
|
||||||
"@entryActionRotateScreen": {},
|
"@entryActionRotateScreen": {},
|
||||||
"entryActionViewSource": "Паглядзець крыніцу",
|
"entryActionViewSource": "Паглядзець крыніцу",
|
||||||
"@entryActionViewSource": {},
|
"@entryActionViewSource": {},
|
||||||
"entryActionConvertMotionPhotoToStillImage": "Пераўтварыць ў нерухомую выяву",
|
"entryActionConvertMotionPhotoToStillImage": "Канвертаваць у статычны малюнак",
|
||||||
"@entryActionConvertMotionPhotoToStillImage": {},
|
"@entryActionConvertMotionPhotoToStillImage": {},
|
||||||
"entryActionViewMotionPhotoVideo": "Адкрыць відэа",
|
"entryActionViewMotionPhotoVideo": "Адкрыць відэа",
|
||||||
"@entryActionViewMotionPhotoVideo": {},
|
"@entryActionViewMotionPhotoVideo": {},
|
||||||
"entryActionSetAs": "Ўсталяваць як",
|
"entryActionSetAs": "Усталяваць як",
|
||||||
"@entryActionSetAs": {},
|
"@entryActionSetAs": {},
|
||||||
"entryActionAddFavourite": "Дадаць ў абранае",
|
"entryActionAddFavourite": "Дадаць у абранае",
|
||||||
"@entryActionAddFavourite": {},
|
"@entryActionAddFavourite": {},
|
||||||
"videoActionUnmute": "Ўключыць гук",
|
"videoActionUnmute": "Уключыць гук",
|
||||||
"@videoActionUnmute": {},
|
"@videoActionUnmute": {},
|
||||||
"videoActionCaptureFrame": "Захоп кадра",
|
"videoActionCaptureFrame": "Захоп кадра",
|
||||||
"@videoActionCaptureFrame": {},
|
"@videoActionCaptureFrame": {},
|
||||||
|
@ -188,11 +188,11 @@
|
||||||
"@entryActionEdit": {},
|
"@entryActionEdit": {},
|
||||||
"entryActionOpen": "Адкрыць з дапамогай",
|
"entryActionOpen": "Адкрыць з дапамогай",
|
||||||
"@entryActionOpen": {},
|
"@entryActionOpen": {},
|
||||||
"entryActionOpenMap": "Паказаць ў праграме карты",
|
"entryActionOpenMap": "Паказаць у праграме карты",
|
||||||
"@entryActionOpenMap": {},
|
"@entryActionOpenMap": {},
|
||||||
"videoActionMute": "Адключыць гук",
|
"videoActionMute": "Адключыць гук",
|
||||||
"@videoActionMute": {},
|
"@videoActionMute": {},
|
||||||
"slideshowActionShowInCollection": "Паказаць ў Калекцыі",
|
"slideshowActionShowInCollection": "Паказаць у Калекцыі",
|
||||||
"@slideshowActionShowInCollection": {},
|
"@slideshowActionShowInCollection": {},
|
||||||
"entryInfoActionEditDate": "Рэдагаваць дату і час",
|
"entryInfoActionEditDate": "Рэдагаваць дату і час",
|
||||||
"@entryInfoActionEditDate": {},
|
"@entryInfoActionEditDate": {},
|
||||||
|
@ -228,7 +228,7 @@
|
||||||
"@filterTypeSphericalVideoLabel": {},
|
"@filterTypeSphericalVideoLabel": {},
|
||||||
"filterNoTitleLabel": "Без назвы",
|
"filterNoTitleLabel": "Без назвы",
|
||||||
"@filterNoTitleLabel": {},
|
"@filterNoTitleLabel": {},
|
||||||
"filterOnThisDayLabel": "Ў гэты дзень",
|
"filterOnThisDayLabel": "У гэты дзень",
|
||||||
"@filterOnThisDayLabel": {},
|
"@filterOnThisDayLabel": {},
|
||||||
"filterRatingRejectedLabel": "Адхілена",
|
"filterRatingRejectedLabel": "Адхілена",
|
||||||
"@filterRatingRejectedLabel": {},
|
"@filterRatingRejectedLabel": {},
|
||||||
|
@ -363,7 +363,7 @@
|
||||||
"@vaultLockTypePassword": {},
|
"@vaultLockTypePassword": {},
|
||||||
"settingsVideoEnablePip": "Карцінка ў карцінцы",
|
"settingsVideoEnablePip": "Карцінка ў карцінцы",
|
||||||
"@settingsVideoEnablePip": {},
|
"@settingsVideoEnablePip": {},
|
||||||
"videoControlsPlayOutside": "Адкрыць ў іншым прайгравальніку",
|
"videoControlsPlayOutside": "Адкрыць у іншым прайгравальніку",
|
||||||
"@videoControlsPlayOutside": {},
|
"@videoControlsPlayOutside": {},
|
||||||
"videoControlsPlay": "Прайграванне",
|
"videoControlsPlay": "Прайграванне",
|
||||||
"@videoControlsPlay": {},
|
"@videoControlsPlay": {},
|
||||||
|
@ -449,7 +449,7 @@
|
||||||
"@wallpaperTargetHomeLock": {},
|
"@wallpaperTargetHomeLock": {},
|
||||||
"widgetTapUpdateWidget": "Абнавіць віджэт",
|
"widgetTapUpdateWidget": "Абнавіць віджэт",
|
||||||
"@widgetTapUpdateWidget": {},
|
"@widgetTapUpdateWidget": {},
|
||||||
"storageVolumeDescriptionFallbackPrimary": "Ўнутраная памяць",
|
"storageVolumeDescriptionFallbackPrimary": "Унутраная памяць",
|
||||||
"@storageVolumeDescriptionFallbackPrimary": {},
|
"@storageVolumeDescriptionFallbackPrimary": {},
|
||||||
"restrictedAccessDialogMessage": "Гэтай праграме забаронена змяняць файлы ў {directory} «{volume}».\n\nКаб перамясціць элементы ў іншую дырэкторыю, выкарыстоўвайце папярэдне ўсталяваны дыспетчар файлаў або праграму галерэі.",
|
"restrictedAccessDialogMessage": "Гэтай праграме забаронена змяняць файлы ў {directory} «{volume}».\n\nКаб перамясціць элементы ў іншую дырэкторыю, выкарыстоўвайце папярэдне ўсталяваны дыспетчар файлаў або праграму галерэі.",
|
||||||
"@restrictedAccessDialogMessage": {
|
"@restrictedAccessDialogMessage": {
|
||||||
|
@ -465,7 +465,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"missingSystemFilePickerDialogMessage": "Сродак выбару сістэмных файлаў адсутнічае або адключаны. Ўключыце яго і паўтарыце спробу.",
|
"missingSystemFilePickerDialogMessage": "Сістэмная праграма выбару файлаў адсутнічае ці адключана. Калі ласка, уключыце яе і паспрабуйце яшчэ раз.",
|
||||||
"@missingSystemFilePickerDialogMessage": {},
|
"@missingSystemFilePickerDialogMessage": {},
|
||||||
"unsupportedTypeDialogMessage": "{count, plural, =1{Гэта аперацыя не падтрымліваецца для элементаў наступнага тыпу: {types}.} other{Гэта аперацыя не падтрымліваецца для элементаў наступных тыпаў: {types}.}}",
|
"unsupportedTypeDialogMessage": "{count, plural, =1{Гэта аперацыя не падтрымліваецца для элементаў наступнага тыпу: {types}.} other{Гэта аперацыя не падтрымліваецца для элементаў наступных тыпаў: {types}.}}",
|
||||||
"@unsupportedTypeDialogMessage": {
|
"@unsupportedTypeDialogMessage": {
|
||||||
|
@ -488,7 +488,7 @@
|
||||||
"@moveUndatedConfirmationDialogMessage": {},
|
"@moveUndatedConfirmationDialogMessage": {},
|
||||||
"moveUndatedConfirmationDialogSetDate": "Захаваць даты",
|
"moveUndatedConfirmationDialogSetDate": "Захаваць даты",
|
||||||
"@moveUndatedConfirmationDialogSetDate": {},
|
"@moveUndatedConfirmationDialogSetDate": {},
|
||||||
"videoResumeDialogMessage": "Вы хочаце аднавіць гульню ў {time}?",
|
"videoResumeDialogMessage": "Вы хочаце аднавіць прайграванне на {time}?",
|
||||||
"@videoResumeDialogMessage": {
|
"@videoResumeDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"time": {
|
"time": {
|
||||||
|
@ -517,15 +517,15 @@
|
||||||
"@configureVaultDialogTitle": {},
|
"@configureVaultDialogTitle": {},
|
||||||
"vaultDialogLockTypeLabel": "Тып блакіроўкі",
|
"vaultDialogLockTypeLabel": "Тып блакіроўкі",
|
||||||
"@vaultDialogLockTypeLabel": {},
|
"@vaultDialogLockTypeLabel": {},
|
||||||
"pinDialogEnter": "Ўвядзіце PIN-код",
|
"pinDialogEnter": "Увядзіце PIN-код",
|
||||||
"@pinDialogEnter": {},
|
"@pinDialogEnter": {},
|
||||||
"patternDialogEnter": "Ўвядзіце графічны ключ",
|
"patternDialogEnter": "Увядзіце ключ",
|
||||||
"@patternDialogEnter": {},
|
"@patternDialogEnter": {},
|
||||||
"patternDialogConfirm": "Пацвердзіце графічны ключ",
|
"patternDialogConfirm": "Пацвердзіце графічны ключ",
|
||||||
"@patternDialogConfirm": {},
|
"@patternDialogConfirm": {},
|
||||||
"pinDialogConfirm": "Пацвердзіце PIN-код",
|
"pinDialogConfirm": "Пацвердзіце PIN-код",
|
||||||
"@pinDialogConfirm": {},
|
"@pinDialogConfirm": {},
|
||||||
"passwordDialogEnter": "Ўвядзіце пароль",
|
"passwordDialogEnter": "Увядзіце пароль",
|
||||||
"@passwordDialogEnter": {},
|
"@passwordDialogEnter": {},
|
||||||
"passwordDialogConfirm": "Пацвердзіце пароль",
|
"passwordDialogConfirm": "Пацвердзіце пароль",
|
||||||
"@passwordDialogConfirm": {},
|
"@passwordDialogConfirm": {},
|
||||||
|
@ -551,7 +551,7 @@
|
||||||
"@mapPointNorthUpTooltip": {},
|
"@mapPointNorthUpTooltip": {},
|
||||||
"viewerInfoLabelCoordinates": "Каардынаты",
|
"viewerInfoLabelCoordinates": "Каардынаты",
|
||||||
"@viewerInfoLabelCoordinates": {},
|
"@viewerInfoLabelCoordinates": {},
|
||||||
"viewerInfoLabelOwner": "Ўладальнік",
|
"viewerInfoLabelOwner": "Уладальнік",
|
||||||
"@viewerInfoLabelOwner": {},
|
"@viewerInfoLabelOwner": {},
|
||||||
"viewerInfoLabelDuration": "Працягласць",
|
"viewerInfoLabelDuration": "Працягласць",
|
||||||
"@viewerInfoLabelDuration": {},
|
"@viewerInfoLabelDuration": {},
|
||||||
|
@ -577,7 +577,7 @@
|
||||||
"@sourceViewerPageTitle": {},
|
"@sourceViewerPageTitle": {},
|
||||||
"panoramaDisableSensorControl": "Адключыць сэнсарнае кіраванне",
|
"panoramaDisableSensorControl": "Адключыць сэнсарнае кіраванне",
|
||||||
"@panoramaDisableSensorControl": {},
|
"@panoramaDisableSensorControl": {},
|
||||||
"panoramaEnableSensorControl": "Ўключыць сэнсарнае кіраванне",
|
"panoramaEnableSensorControl": "Уключыць сэнсарнае кіраванне",
|
||||||
"@panoramaEnableSensorControl": {},
|
"@panoramaEnableSensorControl": {},
|
||||||
"tagPlaceholderPlace": "Месца",
|
"tagPlaceholderPlace": "Месца",
|
||||||
"@tagPlaceholderPlace": {},
|
"@tagPlaceholderPlace": {},
|
||||||
|
@ -601,7 +601,7 @@
|
||||||
"@videoControlsNone": {},
|
"@videoControlsNone": {},
|
||||||
"viewerErrorUnknown": "Ой!",
|
"viewerErrorUnknown": "Ой!",
|
||||||
"@viewerErrorUnknown": {},
|
"@viewerErrorUnknown": {},
|
||||||
"viewerSetWallpaperButtonLabel": "ЎСТАНАВІЦЬ ШПАЛЕРЫ",
|
"viewerSetWallpaperButtonLabel": "УСТАНАВІЦЬ ШПАЛЕРЫ",
|
||||||
"@viewerSetWallpaperButtonLabel": {},
|
"@viewerSetWallpaperButtonLabel": {},
|
||||||
"statsTopAlbumsSectionTitle": "Лепшыя альбомы",
|
"statsTopAlbumsSectionTitle": "Лепшыя альбомы",
|
||||||
"@statsTopAlbumsSectionTitle": {},
|
"@statsTopAlbumsSectionTitle": {},
|
||||||
|
@ -625,7 +625,7 @@
|
||||||
"@mapZoomOutTooltip": {},
|
"@mapZoomOutTooltip": {},
|
||||||
"openMapPageTooltip": "Паглядзець на старонцы карты",
|
"openMapPageTooltip": "Паглядзець на старонцы карты",
|
||||||
"@openMapPageTooltip": {},
|
"@openMapPageTooltip": {},
|
||||||
"mapEmptyRegion": "Ў гэтым рэгіёне няма малюнкаў",
|
"mapEmptyRegion": "Няма малюнкаў у гэтым рэгіёне",
|
||||||
"@mapEmptyRegion": {},
|
"@mapEmptyRegion": {},
|
||||||
"viewerInfoSearchEmpty": "Няма адпаведных ключоў",
|
"viewerInfoSearchEmpty": "Няма адпаведных ключоў",
|
||||||
"@viewerInfoSearchEmpty": {},
|
"@viewerInfoSearchEmpty": {},
|
||||||
|
@ -685,19 +685,19 @@
|
||||||
"@aboutBugCopyInfoInstruction": {},
|
"@aboutBugCopyInfoInstruction": {},
|
||||||
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць сметніцу.",
|
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць сметніцу.",
|
||||||
"@vaultBinUsageDialogMessage": {},
|
"@vaultBinUsageDialogMessage": {},
|
||||||
"aboutBugSaveLogInstruction": "Захаваць журналы праграмы ў файл",
|
"aboutBugSaveLogInstruction": "Захавайце логі праграмы ў файл",
|
||||||
"@aboutBugSaveLogInstruction": {},
|
"@aboutBugSaveLogInstruction": {},
|
||||||
"aboutBugReportInstruction": "Адправіць справаздачу аб памылцы на GitHub разам з журналамі і сістэмнай інфармацыяй",
|
"aboutBugReportInstruction": "Адправіць справаздачу аб памылцы на GitHub разам з журналамі і сістэмнай інфармацыяй",
|
||||||
"@aboutBugReportInstruction": {},
|
"@aboutBugReportInstruction": {},
|
||||||
"entryActionCast": "Трансляцыя",
|
"entryActionCast": "Трансляцыя",
|
||||||
"@entryActionCast": {},
|
"@entryActionCast": {},
|
||||||
"hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце убачыць іх зноў ў наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
|
"hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце паказаць іх зноў у наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
|
||||||
"@hideFilterConfirmationDialogMessage": {},
|
"@hideFilterConfirmationDialogMessage": {},
|
||||||
"renameEntrySetPagePatternFieldLabel": "Шаблон наймення",
|
"renameEntrySetPagePatternFieldLabel": "Шаблон наймення",
|
||||||
"@renameEntrySetPagePatternFieldLabel": {},
|
"@renameEntrySetPagePatternFieldLabel": {},
|
||||||
"renameAlbumDialogLabel": "Новая назва",
|
"renameAlbumDialogLabel": "Новая назва",
|
||||||
"@renameAlbumDialogLabel": {},
|
"@renameAlbumDialogLabel": {},
|
||||||
"renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ўжо ёсць",
|
"renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ужо існуе",
|
||||||
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
|
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
|
||||||
"aboutBugReportButton": "Адправіць справаздачу",
|
"aboutBugReportButton": "Адправіць справаздачу",
|
||||||
"@aboutBugReportButton": {},
|
"@aboutBugReportButton": {},
|
||||||
|
@ -707,7 +707,7 @@
|
||||||
"@aboutBugSectionTitle": {},
|
"@aboutBugSectionTitle": {},
|
||||||
"aboutBugCopyInfoButton": "Скапіяваць",
|
"aboutBugCopyInfoButton": "Скапіяваць",
|
||||||
"@aboutBugCopyInfoButton": {},
|
"@aboutBugCopyInfoButton": {},
|
||||||
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент ў сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў ў сметніцу?}}",
|
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент у сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў у сметніцу?}}",
|
||||||
"@binEntriesConfirmationDialogMessage": {
|
"@binEntriesConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -797,7 +797,7 @@
|
||||||
"@settingsCollectionTile": {},
|
"@settingsCollectionTile": {},
|
||||||
"settingsThemeBrightnessDialogTitle": "Тэма",
|
"settingsThemeBrightnessDialogTitle": "Тэма",
|
||||||
"@settingsThemeBrightnessDialogTitle": {},
|
"@settingsThemeBrightnessDialogTitle": {},
|
||||||
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент ў ім?} few{Выдаліць гэты альбом і {count} элементы ў ім?} other{Выдаліць гэты альбом і {count} элементаў ў ім?}}",
|
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент у ім?} few{Выдаліць гэты альбом і {count} элементы ў ім?} other{Выдаліць гэты альбом і {count} элементаў у ім?}}",
|
||||||
"@deleteSingleAlbumConfirmationDialogMessage": {
|
"@deleteSingleAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -819,7 +819,7 @@
|
||||||
"@aboutDataUsageMisc": {},
|
"@aboutDataUsageMisc": {},
|
||||||
"albumVideoCaptures": "Відэазапісы",
|
"albumVideoCaptures": "Відэазапісы",
|
||||||
"@albumVideoCaptures": {},
|
"@albumVideoCaptures": {},
|
||||||
"editEntryDateDialogSetCustom": "Ўсталяваць карыстацкую дату",
|
"editEntryDateDialogSetCustom": "Устанавіць дату",
|
||||||
"@editEntryDateDialogSetCustom": {},
|
"@editEntryDateDialogSetCustom": {},
|
||||||
"settingsSearchEmpty": "Няма адпаведнай налады",
|
"settingsSearchEmpty": "Няма адпаведнай налады",
|
||||||
"@settingsSearchEmpty": {},
|
"@settingsSearchEmpty": {},
|
||||||
|
@ -845,7 +845,7 @@
|
||||||
"@collectionSelectSectionTooltip": {},
|
"@collectionSelectSectionTooltip": {},
|
||||||
"aboutLicensesBanner": "Гэта праграма выкарыстоўвае наступныя пакеты і бібліятэкі з адкрытым зыходным кодам.",
|
"aboutLicensesBanner": "Гэта праграма выкарыстоўвае наступныя пакеты і бібліятэкі з адкрытым зыходным кодам.",
|
||||||
"@aboutLicensesBanner": {},
|
"@aboutLicensesBanner": {},
|
||||||
"dateYesterday": "Ўчора",
|
"dateYesterday": "Учора",
|
||||||
"@dateYesterday": {},
|
"@dateYesterday": {},
|
||||||
"aboutDataUsageDatabase": "База дадзеных",
|
"aboutDataUsageDatabase": "База дадзеных",
|
||||||
"@aboutDataUsageDatabase": {},
|
"@aboutDataUsageDatabase": {},
|
||||||
|
@ -853,7 +853,7 @@
|
||||||
"@tileLayoutMosaic": {},
|
"@tileLayoutMosaic": {},
|
||||||
"collectionDeselectSectionTooltip": "Адмяніць выбар раздзела",
|
"collectionDeselectSectionTooltip": "Адмяніць выбар раздзела",
|
||||||
"@collectionDeselectSectionTooltip": {},
|
"@collectionDeselectSectionTooltip": {},
|
||||||
"settingsKeepScreenOnTile": "Трымаць экран ўключаным",
|
"settingsKeepScreenOnTile": "Трымаць экран уключаным",
|
||||||
"@settingsKeepScreenOnTile": {},
|
"@settingsKeepScreenOnTile": {},
|
||||||
"tileLayoutGrid": "Сетка",
|
"tileLayoutGrid": "Сетка",
|
||||||
"@tileLayoutGrid": {},
|
"@tileLayoutGrid": {},
|
||||||
|
@ -879,11 +879,11 @@
|
||||||
"@videoStreamSelectionDialogAudio": {},
|
"@videoStreamSelectionDialogAudio": {},
|
||||||
"videoSpeedDialogLabel": "Хуткасць прайгравання",
|
"videoSpeedDialogLabel": "Хуткасць прайгравання",
|
||||||
"@videoSpeedDialogLabel": {},
|
"@videoSpeedDialogLabel": {},
|
||||||
"editEntryLocationDialogSetCustom": "Ўстанавіць карыстацкае месцазнаходжанне",
|
"editEntryLocationDialogSetCustom": "Рэдагаваць месцазнаходжанне",
|
||||||
"@editEntryLocationDialogSetCustom": {},
|
"@editEntryLocationDialogSetCustom": {},
|
||||||
"placeEmpty": "Няма месцаў",
|
"placeEmpty": "Няма месцаў",
|
||||||
"@placeEmpty": {},
|
"@placeEmpty": {},
|
||||||
"editEntryDateDialogExtractFromTitle": "Выняць з загалоўка",
|
"editEntryDateDialogExtractFromTitle": "Выняць з назвы",
|
||||||
"@editEntryDateDialogExtractFromTitle": {},
|
"@editEntryDateDialogExtractFromTitle": {},
|
||||||
"aboutLinkLicense": "Ліцэнзія",
|
"aboutLinkLicense": "Ліцэнзія",
|
||||||
"@aboutLinkLicense": {},
|
"@aboutLinkLicense": {},
|
||||||
|
@ -925,7 +925,7 @@
|
||||||
"@drawerAlbumPage": {},
|
"@drawerAlbumPage": {},
|
||||||
"settingsActionImport": "Імпарт",
|
"settingsActionImport": "Імпарт",
|
||||||
"@settingsActionImport": {},
|
"@settingsActionImport": {},
|
||||||
"locationPickerUseThisLocationButton": "Выкарыстоўваць гэтае месца",
|
"locationPickerUseThisLocationButton": "Выкарыстоўваць гэтае месцазнаходжанне",
|
||||||
"@locationPickerUseThisLocationButton": {},
|
"@locationPickerUseThisLocationButton": {},
|
||||||
"collectionGroupNone": "Не групаваць",
|
"collectionGroupNone": "Не групаваць",
|
||||||
"@collectionGroupNone": {},
|
"@collectionGroupNone": {},
|
||||||
|
@ -937,7 +937,7 @@
|
||||||
"@settingsActionImportDialogTitle": {},
|
"@settingsActionImportDialogTitle": {},
|
||||||
"albumGroupTier": "Па ўзроўні",
|
"albumGroupTier": "Па ўзроўні",
|
||||||
"@albumGroupTier": {},
|
"@albumGroupTier": {},
|
||||||
"drawerCollectionAll": "Ўся калекцыя",
|
"drawerCollectionAll": "Уся калекцыя",
|
||||||
"@drawerCollectionAll": {},
|
"@drawerCollectionAll": {},
|
||||||
"sortByItemCount": "Па колькасці элементаў",
|
"sortByItemCount": "Па колькасці элементаў",
|
||||||
"@sortByItemCount": {},
|
"@sortByItemCount": {},
|
||||||
|
@ -953,7 +953,7 @@
|
||||||
"@albumPickPageTitlePick": {},
|
"@albumPickPageTitlePick": {},
|
||||||
"menuActionMap": "Карта",
|
"menuActionMap": "Карта",
|
||||||
"@menuActionMap": {},
|
"@menuActionMap": {},
|
||||||
"collectionActionMove": "Перамясціць ў альбом",
|
"collectionActionMove": "Перамясціць у альбом",
|
||||||
"@collectionActionMove": {},
|
"@collectionActionMove": {},
|
||||||
"searchAlbumsSectionTitle": "Альбомы",
|
"searchAlbumsSectionTitle": "Альбомы",
|
||||||
"@searchAlbumsSectionTitle": {},
|
"@searchAlbumsSectionTitle": {},
|
||||||
|
@ -1013,9 +1013,9 @@
|
||||||
"@albumPageTitle": {},
|
"@albumPageTitle": {},
|
||||||
"editEntryLocationDialogTitle": "Месцазнаходжанне",
|
"editEntryLocationDialogTitle": "Месцазнаходжанне",
|
||||||
"@editEntryLocationDialogTitle": {},
|
"@editEntryLocationDialogTitle": {},
|
||||||
"albumPickPageTitleCopy": "Скапіяваць ў альбом",
|
"albumPickPageTitleCopy": "Капіяваць у альбом",
|
||||||
"@albumPickPageTitleCopy": {},
|
"@albumPickPageTitleCopy": {},
|
||||||
"collectionActionCopy": "Скапіяваць ў альбом",
|
"collectionActionCopy": "Скапіяваць у альбом",
|
||||||
"@collectionActionCopy": {},
|
"@collectionActionCopy": {},
|
||||||
"viewDialogReverseSortOrder": "Адваротны парадак сартавання",
|
"viewDialogReverseSortOrder": "Адваротны парадак сартавання",
|
||||||
"@viewDialogReverseSortOrder": {},
|
"@viewDialogReverseSortOrder": {},
|
||||||
|
@ -1033,7 +1033,7 @@
|
||||||
"@tagEmpty": {},
|
"@tagEmpty": {},
|
||||||
"collectionActionShowTitleSearch": "Паказаць фільтр загалоўка",
|
"collectionActionShowTitleSearch": "Паказаць фільтр загалоўка",
|
||||||
"@collectionActionShowTitleSearch": {},
|
"@collectionActionShowTitleSearch": {},
|
||||||
"menuActionSelectAll": "Выбраць ўсё",
|
"menuActionSelectAll": "Выбраць усе",
|
||||||
"@menuActionSelectAll": {},
|
"@menuActionSelectAll": {},
|
||||||
"settingsConfirmationTile": "Дыялогі пацверджання",
|
"settingsConfirmationTile": "Дыялогі пацверджання",
|
||||||
"@settingsConfirmationTile": {},
|
"@settingsConfirmationTile": {},
|
||||||
|
@ -1059,7 +1059,7 @@
|
||||||
"@drawerCollectionAnimated": {},
|
"@drawerCollectionAnimated": {},
|
||||||
"durationDialogHours": "Гадзіны",
|
"durationDialogHours": "Гадзіны",
|
||||||
"@durationDialogHours": {},
|
"@durationDialogHours": {},
|
||||||
"settingsKeepScreenOnDialogTitle": "Трымаць экран ўключаным",
|
"settingsKeepScreenOnDialogTitle": "Трымаць экран уключаным",
|
||||||
"@settingsKeepScreenOnDialogTitle": {},
|
"@settingsKeepScreenOnDialogTitle": {},
|
||||||
"drawerPlacePage": "Месцы",
|
"drawerPlacePage": "Месцы",
|
||||||
"@drawerPlacePage": {},
|
"@drawerPlacePage": {},
|
||||||
|
@ -1077,7 +1077,7 @@
|
||||||
"@appExportFavourites": {},
|
"@appExportFavourites": {},
|
||||||
"collectionEmptyImages": "Няма выяў",
|
"collectionEmptyImages": "Няма выяў",
|
||||||
"@collectionEmptyImages": {},
|
"@collectionEmptyImages": {},
|
||||||
"albumPickPageTitleExport": "Экспартаваць ў альбом",
|
"albumPickPageTitleExport": "Экспарт у альбом",
|
||||||
"@albumPickPageTitleExport": {},
|
"@albumPickPageTitleExport": {},
|
||||||
"settingsActionExportDialogTitle": "Экспарт",
|
"settingsActionExportDialogTitle": "Экспарт",
|
||||||
"@settingsActionExportDialogTitle": {},
|
"@settingsActionExportDialogTitle": {},
|
||||||
|
@ -1127,7 +1127,7 @@
|
||||||
"@viewDialogLayoutSectionTitle": {},
|
"@viewDialogLayoutSectionTitle": {},
|
||||||
"searchStatesSectionTitle": "Штаты",
|
"searchStatesSectionTitle": "Штаты",
|
||||||
"@searchStatesSectionTitle": {},
|
"@searchStatesSectionTitle": {},
|
||||||
"dateThisMonth": "Ў гэтым месяцы",
|
"dateThisMonth": "У гэтым месяцы",
|
||||||
"@dateThisMonth": {},
|
"@dateThisMonth": {},
|
||||||
"aboutPageTitle": "Пра нас",
|
"aboutPageTitle": "Пра нас",
|
||||||
"@aboutPageTitle": {},
|
"@aboutPageTitle": {},
|
||||||
|
@ -1141,7 +1141,7 @@
|
||||||
"@genericFailureFeedback": {},
|
"@genericFailureFeedback": {},
|
||||||
"aboutDataUsageData": "Дадзеныя",
|
"aboutDataUsageData": "Дадзеныя",
|
||||||
"@aboutDataUsageData": {},
|
"@aboutDataUsageData": {},
|
||||||
"aboutDataUsageInternal": "Ўнутраны",
|
"aboutDataUsageInternal": "Унутранае",
|
||||||
"@aboutDataUsageInternal": {},
|
"@aboutDataUsageInternal": {},
|
||||||
"albumDownload": "Загрузкі",
|
"albumDownload": "Загрузкі",
|
||||||
"@albumDownload": {},
|
"@albumDownload": {},
|
||||||
|
@ -1149,7 +1149,7 @@
|
||||||
"@coverDialogTabColor": {},
|
"@coverDialogTabColor": {},
|
||||||
"genericSuccessFeedback": "Гатова!",
|
"genericSuccessFeedback": "Гатова!",
|
||||||
"@genericSuccessFeedback": {},
|
"@genericSuccessFeedback": {},
|
||||||
"aboutLicensesShowAllButtonLabel": "Паказаць ўсе ліцэнзіі",
|
"aboutLicensesShowAllButtonLabel": "Паказаць усе ліцэнзіі",
|
||||||
"@aboutLicensesShowAllButtonLabel": {},
|
"@aboutLicensesShowAllButtonLabel": {},
|
||||||
"sortOrderNewestFirst": "Спачатку самае новае",
|
"sortOrderNewestFirst": "Спачатку самае новае",
|
||||||
"@sortOrderNewestFirst": {},
|
"@sortOrderNewestFirst": {},
|
||||||
|
@ -1175,7 +1175,7 @@
|
||||||
"@menuActionStats": {},
|
"@menuActionStats": {},
|
||||||
"appPickDialogTitle": "Выбраць праграму",
|
"appPickDialogTitle": "Выбраць праграму",
|
||||||
"@appPickDialogTitle": {},
|
"@appPickDialogTitle": {},
|
||||||
"albumPickPageTitleMove": "Перамясціць ў альбом",
|
"albumPickPageTitleMove": "Перамясціць у альбом",
|
||||||
"@albumPickPageTitleMove": {},
|
"@albumPickPageTitleMove": {},
|
||||||
"coverDialogTabCover": "Вокладка",
|
"coverDialogTabCover": "Вокладка",
|
||||||
"@coverDialogTabCover": {},
|
"@coverDialogTabCover": {},
|
||||||
|
@ -1183,7 +1183,7 @@
|
||||||
"@settingsConfirmationBeforeDeleteItems": {},
|
"@settingsConfirmationBeforeDeleteItems": {},
|
||||||
"settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты",
|
"settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты",
|
||||||
"@settingsConfirmationBeforeMoveUndatedItems": {},
|
"@settingsConfirmationBeforeMoveUndatedItems": {},
|
||||||
"settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў ў сметніцу",
|
"settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў у сметніцу",
|
||||||
"@settingsConfirmationAfterMoveToBinItems": {},
|
"@settingsConfirmationAfterMoveToBinItems": {},
|
||||||
"settingsConfirmationBeforeMoveToBinItems": "Спытаць перад тым, як пераносіць элементы ў сметніцу",
|
"settingsConfirmationBeforeMoveToBinItems": "Спытаць перад тым, як пераносіць элементы ў сметніцу",
|
||||||
"@settingsConfirmationBeforeMoveToBinItems": {},
|
"@settingsConfirmationBeforeMoveToBinItems": {},
|
||||||
|
@ -1387,7 +1387,7 @@
|
||||||
"@settingsNavigationDrawerTile": {},
|
"@settingsNavigationDrawerTile": {},
|
||||||
"settingsHiddenItemsPageTitle": "Схаваныя элементы",
|
"settingsHiddenItemsPageTitle": "Схаваныя элементы",
|
||||||
"@settingsHiddenItemsPageTitle": {},
|
"@settingsHiddenItemsPageTitle": {},
|
||||||
"settingsHiddenPathsBanner": "Фатаграфіі і відэа ў гэтых папках або ў любой з іх укладзеных папак не будуць адлюстроўвацца ў вашай калекцыі.",
|
"settingsHiddenPathsBanner": "Фатаграфіі і відэа ў гэтых тэчках або ў любой з іх укладзеных тэчках не будуць адлюстроўвацца ў вашай калекцыі.",
|
||||||
"@settingsHiddenPathsBanner": {},
|
"@settingsHiddenPathsBanner": {},
|
||||||
"settingsViewerShowOverlayOnOpening": "Паказаць на адкрыцці",
|
"settingsViewerShowOverlayOnOpening": "Паказаць на адкрыцці",
|
||||||
"@settingsViewerShowOverlayOnOpening": {},
|
"@settingsViewerShowOverlayOnOpening": {},
|
||||||
|
@ -1405,7 +1405,7 @@
|
||||||
"@settingsStorageAccessEmpty": {},
|
"@settingsStorageAccessEmpty": {},
|
||||||
"settingsRemoveAnimationsTile": "Выдаліць анімацыі",
|
"settingsRemoveAnimationsTile": "Выдаліць анімацыі",
|
||||||
"@settingsRemoveAnimationsTile": {},
|
"@settingsRemoveAnimationsTile": {},
|
||||||
"settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў ў іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
|
"settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў у іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
|
||||||
"@settingsStorageAccessBanner": {},
|
"@settingsStorageAccessBanner": {},
|
||||||
"collectionCopySuccessFeedback": "{count, plural, =1{1 элемент скапіяваны} few{{count} элементы скапіявана} other{{count} элементаў скапіявана}}",
|
"collectionCopySuccessFeedback": "{count, plural, =1{1 элемент скапіяваны} few{{count} элементы скапіявана} other{{count} элементаў скапіявана}}",
|
||||||
"@collectionCopySuccessFeedback": {
|
"@collectionCopySuccessFeedback": {
|
||||||
|
@ -1467,7 +1467,7 @@
|
||||||
"@settingsSubtitleThemeTextPositionTile": {},
|
"@settingsSubtitleThemeTextPositionTile": {},
|
||||||
"settingsVideoBackgroundModeDialogTitle": "Фонавы рэжым",
|
"settingsVideoBackgroundModeDialogTitle": "Фонавы рэжым",
|
||||||
"@settingsVideoBackgroundModeDialogTitle": {},
|
"@settingsVideoBackgroundModeDialogTitle": {},
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент ў іх?} few{Выдаліць гэтыя альбомы і {count} элементы ў іх?} other{Выдаліць гэтыя альбомы і {count} элементаў ў іх?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент у іх?} few{Выдаліць гэтыя альбомы і {count} элементы ў іх?} other{Выдаліць гэтыя альбомы і {count} элементаў у іх?}}",
|
||||||
"@deleteMultiAlbumConfirmationDialogMessage": {
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -1519,24 +1519,28 @@
|
||||||
"minutes": {}
|
"minutes": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"collectionActionSetHome": "Ўсталяваць як галоўную",
|
"collectionActionSetHome": "Усталяваць як галоўную",
|
||||||
"@collectionActionSetHome": {},
|
"@collectionActionSetHome": {},
|
||||||
"setHomeCustomCollection": "Ўласная калекцыя",
|
"setHomeCustomCollection": "Уласная калекцыя",
|
||||||
"@setHomeCustomCollection": {},
|
"@setHomeCustomCollection": {},
|
||||||
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
|
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
|
||||||
"@settingsThumbnailShowHdrIcon": {},
|
"@settingsThumbnailShowHdrIcon": {},
|
||||||
"videoRepeatActionSetEnd": "Ўсталяваць канец",
|
"videoRepeatActionSetEnd": "Усталяваць канец",
|
||||||
"@videoRepeatActionSetEnd": {},
|
"@videoRepeatActionSetEnd": {},
|
||||||
"stopTooltip": "Спыніць",
|
"stopTooltip": "Спыніць",
|
||||||
"@stopTooltip": {},
|
"@stopTooltip": {},
|
||||||
"videoActionABRepeat": "Паўтарыць ад А да Б",
|
"videoActionABRepeat": "Паўтарыць ад А да Б",
|
||||||
"@videoActionABRepeat": {},
|
"@videoActionABRepeat": {},
|
||||||
"videoRepeatActionSetStart": "Ўсталяваць пачатак",
|
"videoRepeatActionSetStart": "Усталяваць пачатак",
|
||||||
"@videoRepeatActionSetStart": {},
|
"@videoRepeatActionSetStart": {},
|
||||||
"renameProcessorHash": "Хэш",
|
"renameProcessorHash": "Хэш",
|
||||||
"@renameProcessorHash": {},
|
"@renameProcessorHash": {},
|
||||||
"settingsForceWesternArabicNumeralsTile": "Прымусовыя арабскія лічбы",
|
"settingsForceWesternArabicNumeralsTile": "Прымусовыя арабскія лічбы",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "Паказаць ў Калекцыі",
|
"chipActionShowCollection": "Паказаць у Калекцыі",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "Паказаць у Правадыру",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "Правадыр",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
"chipActionGoToCountryPage": "Show in Countries",
|
"chipActionGoToCountryPage": "Show in Countries",
|
||||||
"chipActionGoToPlacePage": "Show in Places",
|
"chipActionGoToPlacePage": "Show in Places",
|
||||||
"chipActionGoToTagPage": "Show in Tags",
|
"chipActionGoToTagPage": "Show in Tags",
|
||||||
|
"chipActionGoToExplorerPage": "Show in Explorer",
|
||||||
"chipActionFilterOut": "Filter out",
|
"chipActionFilterOut": "Filter out",
|
||||||
"chipActionFilterIn": "Filter in",
|
"chipActionFilterIn": "Filter in",
|
||||||
"chipActionHide": "Hide",
|
"chipActionHide": "Hide",
|
||||||
|
@ -771,6 +772,8 @@
|
||||||
|
|
||||||
"binPageTitle": "Recycle Bin",
|
"binPageTitle": "Recycle Bin",
|
||||||
|
|
||||||
|
"explorerPageTitle": "Explorer",
|
||||||
|
|
||||||
"searchCollectionFieldHint": "Search collection",
|
"searchCollectionFieldHint": "Search collection",
|
||||||
"searchRecentSectionTitle": "Recent",
|
"searchRecentSectionTitle": "Recent",
|
||||||
"searchDateSectionTitle": "Date",
|
"searchDateSectionTitle": "Date",
|
||||||
|
|
|
@ -1380,5 +1380,9 @@
|
||||||
"renameProcessorHash": "Hash",
|
"renameProcessorHash": "Hash",
|
||||||
"@renameProcessorHash": {},
|
"@renameProcessorHash": {},
|
||||||
"chipActionShowCollection": "Mostrar en Colección",
|
"chipActionShowCollection": "Mostrar en Colección",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"explorerPageTitle": "Explorar",
|
||||||
|
"@explorerPageTitle": {},
|
||||||
|
"chipActionGoToExplorerPage": "Mostrar en el explorador",
|
||||||
|
"@chipActionGoToExplorerPage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,5 +302,137 @@
|
||||||
"filterNoDateLabel": "Päiväämätön",
|
"filterNoDateLabel": "Päiväämätön",
|
||||||
"@filterNoDateLabel": {},
|
"@filterNoDateLabel": {},
|
||||||
"chipActionShowCollection": "Näytä kokoelmassa",
|
"chipActionShowCollection": "Näytä kokoelmassa",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"widgetDisplayedItemMostRecent": "Viimeisin",
|
||||||
|
"@widgetDisplayedItemMostRecent": {},
|
||||||
|
"otherDirectoryDescription": "“{name}” kansio",
|
||||||
|
"@otherDirectoryDescription": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "Pictures",
|
||||||
|
"description": "the name of a specific directory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"videoActionABRepeat": "A-B toisto",
|
||||||
|
"@videoActionABRepeat": {},
|
||||||
|
"videoRepeatActionSetStart": "Aseta alku",
|
||||||
|
"@videoRepeatActionSetStart": {},
|
||||||
|
"videoRepeatActionSetEnd": "Aseta loppu",
|
||||||
|
"@videoRepeatActionSetEnd": {},
|
||||||
|
"filterTypeRawLabel": "Raw",
|
||||||
|
"@filterTypeRawLabel": {},
|
||||||
|
"filterTypeSphericalVideoLabel": "360° Video",
|
||||||
|
"@filterTypeSphericalVideoLabel": {},
|
||||||
|
"filterTypeGeotiffLabel": "GeoTIFF",
|
||||||
|
"@filterTypeGeotiffLabel": {},
|
||||||
|
"filterMimeVideoLabel": "Video",
|
||||||
|
"@filterMimeVideoLabel": {},
|
||||||
|
"coordinateFormatDms": "DMS",
|
||||||
|
"@coordinateFormatDms": {},
|
||||||
|
"coordinateDms": "{coordinate} {direction}",
|
||||||
|
"@coordinateDms": {
|
||||||
|
"placeholders": {
|
||||||
|
"coordinate": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "38° 41′ 47.72″"
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "S"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coordinateDmsNorth": "P",
|
||||||
|
"@coordinateDmsNorth": {},
|
||||||
|
"lengthUnitPixel": "px",
|
||||||
|
"@lengthUnitPixel": {},
|
||||||
|
"lengthUnitPercent": "%",
|
||||||
|
"@lengthUnitPercent": {},
|
||||||
|
"mapStyleGoogleNormal": "Google Maps",
|
||||||
|
"@mapStyleGoogleNormal": {},
|
||||||
|
"mapStyleHuaweiNormal": "Petal Maps",
|
||||||
|
"@mapStyleHuaweiNormal": {},
|
||||||
|
"mapStyleHuaweiTerrain": "Petal Maps (Maasto)",
|
||||||
|
"@mapStyleHuaweiTerrain": {},
|
||||||
|
"overlayHistogramRGB": "RGB",
|
||||||
|
"@overlayHistogramRGB": {},
|
||||||
|
"subtitlePositionTop": "Ylhäällä",
|
||||||
|
"@subtitlePositionTop": {},
|
||||||
|
"subtitlePositionBottom": "Alhaalla",
|
||||||
|
"@subtitlePositionBottom": {},
|
||||||
|
"themeBrightnessLight": "Vaalea",
|
||||||
|
"@themeBrightnessLight": {},
|
||||||
|
"themeBrightnessDark": "Tumma",
|
||||||
|
"@themeBrightnessDark": {},
|
||||||
|
"themeBrightnessBlack": "Musta",
|
||||||
|
"@themeBrightnessBlack": {},
|
||||||
|
"unitSystemMetric": "Metrinen",
|
||||||
|
"@unitSystemMetric": {},
|
||||||
|
"unitSystemImperial": "Brittiläinen",
|
||||||
|
"@unitSystemImperial": {},
|
||||||
|
"vaultLockTypePattern": "Kuvio",
|
||||||
|
"@vaultLockTypePattern": {},
|
||||||
|
"vaultLockTypePin": "PIN",
|
||||||
|
"@vaultLockTypePin": {},
|
||||||
|
"vaultLockTypePassword": "Salasana",
|
||||||
|
"@vaultLockTypePassword": {},
|
||||||
|
"settingsVideoEnablePip": "Kuva kuvassa",
|
||||||
|
"@settingsVideoEnablePip": {},
|
||||||
|
"videoControlsPlay": "Toista",
|
||||||
|
"@videoControlsPlay": {},
|
||||||
|
"videoControlsPlayOutside": "Avaa toisella soittimella",
|
||||||
|
"@videoControlsPlayOutside": {},
|
||||||
|
"videoControlsPlaySeek": "Toista & selaa eteen/taakse",
|
||||||
|
"@videoControlsPlaySeek": {},
|
||||||
|
"videoControlsNone": "Ei mitään",
|
||||||
|
"@videoControlsNone": {},
|
||||||
|
"videoLoopModeNever": "Ei koskaan",
|
||||||
|
"@videoLoopModeNever": {},
|
||||||
|
"videoLoopModeShortOnly": "Vain lyhyissä videoissa",
|
||||||
|
"@videoLoopModeShortOnly": {},
|
||||||
|
"videoPlaybackSkip": "Ohita",
|
||||||
|
"@videoPlaybackSkip": {},
|
||||||
|
"videoPlaybackMuted": "Toista mykistettynä",
|
||||||
|
"@videoPlaybackMuted": {},
|
||||||
|
"videoPlaybackWithSound": "Toista äänillä",
|
||||||
|
"@videoPlaybackWithSound": {},
|
||||||
|
"videoResumptionModeAlways": "Aina",
|
||||||
|
"@videoResumptionModeAlways": {},
|
||||||
|
"wallpaperTargetLock": "Lukitusnäyttö",
|
||||||
|
"@wallpaperTargetLock": {},
|
||||||
|
"wallpaperTargetHomeLock": "Koti- ja lukitusnäyttö",
|
||||||
|
"@wallpaperTargetHomeLock": {},
|
||||||
|
"widgetDisplayedItemRandom": "Satunnainen",
|
||||||
|
"@widgetDisplayedItemRandom": {},
|
||||||
|
"focalLength": "{length} mm",
|
||||||
|
"@focalLength": {
|
||||||
|
"placeholders": {
|
||||||
|
"length": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"videoActionUnmute": "Poista mykistys",
|
||||||
|
"@videoActionUnmute": {},
|
||||||
|
"coordinateDmsWest": "L",
|
||||||
|
"@coordinateDmsWest": {},
|
||||||
|
"coordinateDmsSouth": "E",
|
||||||
|
"@coordinateDmsSouth": {},
|
||||||
|
"coordinateDmsEast": "I",
|
||||||
|
"@coordinateDmsEast": {},
|
||||||
|
"videoLoopModeAlways": "Aina",
|
||||||
|
"@videoLoopModeAlways": {},
|
||||||
|
"videoResumptionModeNever": "Ei koskaan",
|
||||||
|
"@videoResumptionModeNever": {},
|
||||||
|
"viewerTransitionNone": "Ei mitään",
|
||||||
|
"@viewerTransitionNone": {},
|
||||||
|
"wallpaperTargetHome": "Kotinäyttö",
|
||||||
|
"@wallpaperTargetHome": {},
|
||||||
|
"storageVolumeDescriptionFallbackPrimary": "Sisäinen tallennustila",
|
||||||
|
"@storageVolumeDescriptionFallbackPrimary": {},
|
||||||
|
"storageVolumeDescriptionFallbackNonPrimary": "SD-kortti",
|
||||||
|
"@storageVolumeDescriptionFallbackNonPrimary": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1380,5 +1380,9 @@
|
||||||
"settingsForceWesternArabicNumeralsTile": "Toujours utiliser les chiffres arabes",
|
"settingsForceWesternArabicNumeralsTile": "Toujours utiliser les chiffres arabes",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "Afficher dans Collection",
|
"chipActionShowCollection": "Afficher dans Collection",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"explorerPageTitle": "Explorateur",
|
||||||
|
"@explorerPageTitle": {},
|
||||||
|
"chipActionGoToExplorerPage": "Afficher dans Explorateur",
|
||||||
|
"@chipActionGoToExplorerPage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1380,5 +1380,9 @@
|
||||||
"settingsForceWesternArabicNumeralsTile": "아라비아 숫자 항상 사용",
|
"settingsForceWesternArabicNumeralsTile": "아라비아 숫자 항상 사용",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "미디어 페이지에서 보기",
|
"chipActionShowCollection": "미디어 페이지에서 보기",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"explorerPageTitle": "탐색기",
|
||||||
|
"@explorerPageTitle": {},
|
||||||
|
"chipActionGoToExplorerPage": "탐색기 페이지에서 보기",
|
||||||
|
"@chipActionGoToExplorerPage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1538,5 +1538,9 @@
|
||||||
"renameProcessorHash": "Skrót",
|
"renameProcessorHash": "Skrót",
|
||||||
"@renameProcessorHash": {},
|
"@renameProcessorHash": {},
|
||||||
"chipActionShowCollection": "Pokaż w Kolekcji",
|
"chipActionShowCollection": "Pokaż w Kolekcji",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "Pokaż w przeglądarce",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "Przeglądarka",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -593,7 +593,7 @@
|
||||||
"@collectionCopySuccessFeedback": {},
|
"@collectionCopySuccessFeedback": {},
|
||||||
"collectionMoveSuccessFeedback": "{count, plural, =1{Перемещён 1 объект} few{Перемещено {count} объекта} other{Перемещено {count} объектов}}",
|
"collectionMoveSuccessFeedback": "{count, plural, =1{Перемещён 1 объект} few{Перемещено {count} объекта} other{Перемещено {count} объектов}}",
|
||||||
"@collectionMoveSuccessFeedback": {},
|
"@collectionMoveSuccessFeedback": {},
|
||||||
"collectionRenameSuccessFeedback": "{count, plural, =1{Переименован 1 объект} few{Переименовао {count} объекта} other{Переименовано {count} объектов}}",
|
"collectionRenameSuccessFeedback": "{count, plural, =1{Переименован 1 объект} few{Переименовано {count} объекта} other{Переименовано {count} объектов}}",
|
||||||
"@collectionRenameSuccessFeedback": {},
|
"@collectionRenameSuccessFeedback": {},
|
||||||
"collectionEditSuccessFeedback": "{count, plural, =1{Изменён 1 объект} few{Изменено {count} объекта} other{Изменено {count} объектов}}",
|
"collectionEditSuccessFeedback": "{count, plural, =1{Изменён 1 объект} few{Изменено {count} объекта} other{Изменено {count} объектов}}",
|
||||||
"@collectionEditSuccessFeedback": {},
|
"@collectionEditSuccessFeedback": {},
|
||||||
|
@ -1380,5 +1380,9 @@
|
||||||
"settingsForceWesternArabicNumeralsTile": "Принудительные арабские цифры",
|
"settingsForceWesternArabicNumeralsTile": "Принудительные арабские цифры",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "Показать в Коллекции",
|
"chipActionShowCollection": "Показать в Коллекции",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "Показать в проводнике",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "Проводник",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1526,5 +1526,17 @@
|
||||||
"settingsThumbnailShowHdrIcon": "Zobraziť ikonu HDR",
|
"settingsThumbnailShowHdrIcon": "Zobraziť ikonu HDR",
|
||||||
"@settingsThumbnailShowHdrIcon": {},
|
"@settingsThumbnailShowHdrIcon": {},
|
||||||
"chipActionShowCollection": "Zobraziť v kolekcií",
|
"chipActionShowCollection": "Zobraziť v kolekcií",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"videoActionABRepeat": "Opakovanie A-B",
|
||||||
|
"@videoActionABRepeat": {},
|
||||||
|
"videoRepeatActionSetStart": "Nastaviť začiatok",
|
||||||
|
"@videoRepeatActionSetStart": {},
|
||||||
|
"videoRepeatActionSetEnd": "Nastaviť koniec",
|
||||||
|
"@videoRepeatActionSetEnd": {},
|
||||||
|
"settingsForceWesternArabicNumeralsTile": "Vynútiť arabské číslice",
|
||||||
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
|
"stopTooltip": "Zastaviť",
|
||||||
|
"@stopTooltip": {},
|
||||||
|
"renameProcessorHash": "Hash",
|
||||||
|
"@renameProcessorHash": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1380,5 +1380,9 @@
|
||||||
"renameProcessorHash": "Sağlama",
|
"renameProcessorHash": "Sağlama",
|
||||||
"@renameProcessorHash": {},
|
"@renameProcessorHash": {},
|
||||||
"chipActionShowCollection": "Koleksiyonda göster",
|
"chipActionShowCollection": "Koleksiyonda göster",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "Gezginde göster",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "Gezgin",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1035,7 +1035,7 @@
|
||||||
"@settingsSubtitleThemeTextAlignmentCenter": {},
|
"@settingsSubtitleThemeTextAlignmentCenter": {},
|
||||||
"settingsSubtitleThemeTextAlignmentRight": "Праворуч",
|
"settingsSubtitleThemeTextAlignmentRight": "Праворуч",
|
||||||
"@settingsSubtitleThemeTextAlignmentRight": {},
|
"@settingsSubtitleThemeTextAlignmentRight": {},
|
||||||
"settingsVideoControlsTile": "Управління",
|
"settingsVideoControlsTile": "Елементи керування",
|
||||||
"@settingsVideoControlsTile": {},
|
"@settingsVideoControlsTile": {},
|
||||||
"settingsVideoButtonsTile": "Кнопки",
|
"settingsVideoButtonsTile": "Кнопки",
|
||||||
"@settingsVideoButtonsTile": {},
|
"@settingsVideoButtonsTile": {},
|
||||||
|
@ -1049,7 +1049,7 @@
|
||||||
"@settingsSaveSearchHistory": {},
|
"@settingsSaveSearchHistory": {},
|
||||||
"settingsEnableBin": "Використовувати кошик",
|
"settingsEnableBin": "Використовувати кошик",
|
||||||
"@settingsEnableBin": {},
|
"@settingsEnableBin": {},
|
||||||
"settingsAllowMediaManagement": "Дозволити управління медіа",
|
"settingsAllowMediaManagement": "Дозволити керування мультимедіа",
|
||||||
"@settingsAllowMediaManagement": {},
|
"@settingsAllowMediaManagement": {},
|
||||||
"settingsHiddenItemsTile": "Приховані елементи",
|
"settingsHiddenItemsTile": "Приховані елементи",
|
||||||
"@settingsHiddenItemsTile": {},
|
"@settingsHiddenItemsTile": {},
|
||||||
|
@ -1297,7 +1297,7 @@
|
||||||
"@settingsSlideshowAnimatedZoomEffect": {},
|
"@settingsSlideshowAnimatedZoomEffect": {},
|
||||||
"settingsSubtitleThemeSample": "Це зразок.",
|
"settingsSubtitleThemeSample": "Це зразок.",
|
||||||
"@settingsSubtitleThemeSample": {},
|
"@settingsSubtitleThemeSample": {},
|
||||||
"settingsVideoControlsPageTitle": "Управління",
|
"settingsVideoControlsPageTitle": "Елементи керування",
|
||||||
"@settingsVideoControlsPageTitle": {},
|
"@settingsVideoControlsPageTitle": {},
|
||||||
"settingsVideoSectionTitle": "Відео",
|
"settingsVideoSectionTitle": "Відео",
|
||||||
"@settingsVideoSectionTitle": {},
|
"@settingsVideoSectionTitle": {},
|
||||||
|
@ -1538,5 +1538,9 @@
|
||||||
"settingsForceWesternArabicNumeralsTile": "Примусові арабські цифри",
|
"settingsForceWesternArabicNumeralsTile": "Примусові арабські цифри",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "Показати у Колекції",
|
"chipActionShowCollection": "Показати у Колекції",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"chipActionGoToExplorerPage": "Показати в провіднику",
|
||||||
|
"@chipActionGoToExplorerPage": {},
|
||||||
|
"explorerPageTitle": "Провідник",
|
||||||
|
"@explorerPageTitle": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1380,5 +1380,9 @@
|
||||||
"settingsForceWesternArabicNumeralsTile": "强制使用阿拉伯数字",
|
"settingsForceWesternArabicNumeralsTile": "强制使用阿拉伯数字",
|
||||||
"@settingsForceWesternArabicNumeralsTile": {},
|
"@settingsForceWesternArabicNumeralsTile": {},
|
||||||
"chipActionShowCollection": "在媒体集中显示",
|
"chipActionShowCollection": "在媒体集中显示",
|
||||||
"@chipActionShowCollection": {}
|
"@chipActionShowCollection": {},
|
||||||
|
"explorerPageTitle": "资源管理器",
|
||||||
|
"@explorerPageTitle": {},
|
||||||
|
"chipActionGoToExplorerPage": "在资源管理器中显示",
|
||||||
|
"@chipActionGoToExplorerPage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,11 +91,14 @@ class Contributors {
|
||||||
Contributor('cheese', 'deanlemans5646@gmail.com'),
|
Contributor('cheese', 'deanlemans5646@gmail.com'),
|
||||||
Contributor('Owen Elderbroek', 'o.elderbroek@gmail.com'),
|
Contributor('Owen Elderbroek', 'o.elderbroek@gmail.com'),
|
||||||
Contributor('Maxi', 'maxitendo01@proton.me'),
|
Contributor('Maxi', 'maxitendo01@proton.me'),
|
||||||
|
Contributor('Jerguš Fonfer', 'caro.jf@protonmail.com'),
|
||||||
|
Contributor('elfriob', 'elfriob@ya.ru'),
|
||||||
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
|
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
|
||||||
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
|
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
|
||||||
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
|
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
|
||||||
// Contributor('Grooty12', 'Rasmus@rosendahl-kaa.name'), // Danish
|
// Contributor('Grooty12', 'Rasmus@rosendahl-kaa.name'), // Danish
|
||||||
// Contributor('Åzze', 'laitinen.jere222@gmail.com'), // Finnish
|
// Contributor('Åzze', 'laitinen.jere222@gmail.com'), // Finnish
|
||||||
|
// Contributor('Olli', 'ollinen@ollit.dev'), // Finnish
|
||||||
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
|
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
|
||||||
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi
|
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi
|
||||||
// Contributor('AJ07', 'ajaykumarmeena676@gmail.com'), // Hindi
|
// Contributor('AJ07', 'ajaykumarmeena676@gmail.com'), // Hindi
|
||||||
|
|
|
@ -63,14 +63,12 @@ class Device {
|
||||||
final auth = LocalAuthentication();
|
final auth = LocalAuthentication();
|
||||||
_canAuthenticateUser = await auth.canCheckBiometrics || await auth.isDeviceSupported();
|
_canAuthenticateUser = await auth.canCheckBiometrics || await auth.isDeviceSupported();
|
||||||
|
|
||||||
final floating = Floating();
|
|
||||||
try {
|
try {
|
||||||
_supportPictureInPicture = await floating.isPipAvailable;
|
_supportPictureInPicture = await Floating().isPipAvailable;
|
||||||
} on PlatformException catch (_) {
|
} on PlatformException catch (_) {
|
||||||
// as of floating v2.0.0, plugin assumes activity and fails when bound via service
|
// as of floating v2.0.0, plugin assumes activity and fails when bound via service
|
||||||
_supportPictureInPicture = false;
|
_supportPictureInPicture = false;
|
||||||
}
|
}
|
||||||
floating.dispose();
|
|
||||||
|
|
||||||
final capabilities = await deviceService.getCapabilities();
|
final capabilities = await deviceService.getCapabilities();
|
||||||
_canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false;
|
_canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false;
|
||||||
|
|
|
@ -44,7 +44,8 @@ class AvesEntry with AvesEntryBase {
|
||||||
AddressDetails? _addressDetails;
|
AddressDetails? _addressDetails;
|
||||||
TrashDetails? trashDetails;
|
TrashDetails? trashDetails;
|
||||||
|
|
||||||
List<AvesEntry>? burstEntries;
|
// synthetic stack of related entries, e.g. burst shots or raw/developed pairs
|
||||||
|
List<AvesEntry>? stackedEntries;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final AChangeNotifier visualChangeNotifier = AChangeNotifier();
|
final AChangeNotifier visualChangeNotifier = AChangeNotifier();
|
||||||
|
@ -69,7 +70,7 @@ class AvesEntry with AvesEntryBase {
|
||||||
required int? durationMillis,
|
required int? durationMillis,
|
||||||
required this.trashed,
|
required this.trashed,
|
||||||
required this.origin,
|
required this.origin,
|
||||||
this.burstEntries,
|
this.stackedEntries,
|
||||||
}) : id = id ?? 0 {
|
}) : id = id ?? 0 {
|
||||||
if (kFlutterMemoryAllocationsEnabled) {
|
if (kFlutterMemoryAllocationsEnabled) {
|
||||||
FlutterMemoryAllocations.instance.dispatchObjectCreated(
|
FlutterMemoryAllocations.instance.dispatchObjectCreated(
|
||||||
|
@ -93,7 +94,7 @@ class AvesEntry with AvesEntryBase {
|
||||||
int? dateAddedSecs,
|
int? dateAddedSecs,
|
||||||
int? dateModifiedSecs,
|
int? dateModifiedSecs,
|
||||||
int? origin,
|
int? origin,
|
||||||
List<AvesEntry>? burstEntries,
|
List<AvesEntry>? stackedEntries,
|
||||||
}) {
|
}) {
|
||||||
final copyEntryId = id ?? this.id;
|
final copyEntryId = id ?? this.id;
|
||||||
final copied = AvesEntry(
|
final copied = AvesEntry(
|
||||||
|
@ -114,7 +115,7 @@ class AvesEntry with AvesEntryBase {
|
||||||
durationMillis: durationMillis,
|
durationMillis: durationMillis,
|
||||||
trashed: trashed,
|
trashed: trashed,
|
||||||
origin: origin ?? this.origin,
|
origin: origin ?? this.origin,
|
||||||
burstEntries: burstEntries ?? this.burstEntries,
|
stackedEntries: stackedEntries ?? this.stackedEntries,
|
||||||
)
|
)
|
||||||
..catalogMetadata = _catalogMetadata?.copyWith(id: copyEntryId)
|
..catalogMetadata = _catalogMetadata?.copyWith(id: copyEntryId)
|
||||||
..addressDetails = _addressDetails?.copyWith(id: copyEntryId)
|
..addressDetails = _addressDetails?.copyWith(id: copyEntryId)
|
||||||
|
|
|
@ -7,9 +7,9 @@ import 'package:aves/services/common/services.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
extension ExtraAvesEntryMultipage on AvesEntry {
|
extension ExtraAvesEntryMultipage on AvesEntry {
|
||||||
bool get isMultiPage => isBurst || ((catalogMetadata?.isMultiPage ?? false) && (isMotionPhoto || !isHdr));
|
bool get isMultiPage => isStack || ((catalogMetadata?.isMultiPage ?? false) && (isMotionPhoto || !isHdr));
|
||||||
|
|
||||||
bool get isBurst => burstEntries?.isNotEmpty == true;
|
bool get isStack => stackedEntries?.isNotEmpty == true;
|
||||||
|
|
||||||
bool get isMotionPhoto => catalogMetadata?.isMotionPhoto ?? false;
|
bool get isMotionPhoto => catalogMetadata?.isMotionPhoto ?? false;
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ extension ExtraAvesEntryMultipage on AvesEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MultiPageInfo?> getMultiPageInfo() async {
|
Future<MultiPageInfo?> getMultiPageInfo() async {
|
||||||
if (isBurst) {
|
if (isStack) {
|
||||||
return MultiPageInfo(
|
return MultiPageInfo(
|
||||||
mainEntry: this,
|
mainEntry: this,
|
||||||
pages: burstEntries!
|
pages: stackedEntries!
|
||||||
.mapIndexed((index, entry) => SinglePageInfo(
|
.mapIndexed((index, entry) => SinglePageInfo(
|
||||||
index: index,
|
index: index,
|
||||||
pageId: entry.id,
|
pageId: entry.id,
|
||||||
|
|
|
@ -52,13 +52,13 @@ class AlbumFilter extends CoveredCollectionFilter {
|
||||||
String getTooltip(BuildContext context) => album;
|
String getTooltip(BuildContext context) => album;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
|
||||||
return IconUtils.getAlbumIcon(
|
return IconUtils.getAlbumIcon(
|
||||||
context: context,
|
context: context,
|
||||||
albumPath: album,
|
albumPath: album,
|
||||||
size: size,
|
size: size,
|
||||||
) ??
|
) ??
|
||||||
(showGenericIcon ? Icon(AIcons.album, size: size) : null);
|
(allowGenericIcon ? Icon(AIcons.album, size: size) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -68,7 +68,7 @@ class AspectRatioFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.aspectRatio, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.aspectRatio, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -69,7 +69,7 @@ class CoordinateFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -122,7 +122,7 @@ class DateFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.date, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.date, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -45,7 +45,7 @@ class FavouriteFilter extends CollectionFilter {
|
||||||
String getLabel(BuildContext context) => context.l10n.filterFavouriteLabel;
|
String getLabel(BuildContext context) => context.l10n.filterFavouriteLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.favourite, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
|
|
@ -133,7 +133,7 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
|
|
||||||
String getTooltip(BuildContext context) => getLabel(context);
|
String getTooltip(BuildContext context) => getLabel(context);
|
||||||
|
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => null;
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => null;
|
||||||
|
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
final colors = context.read<AvesColorsData>();
|
final colors = context.read<AvesColorsData>();
|
||||||
|
|
|
@ -89,7 +89,7 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
String getLabel(BuildContext context) => _isUnlocated ? context.l10n.filterNoLocationLabel : _location;
|
String getLabel(BuildContext context) => _isUnlocated ? context.l10n.filterNoLocationLabel : _location;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
|
||||||
if (_isUnlocated) {
|
if (_isUnlocated) {
|
||||||
return Icon(AIcons.locationUnlocated, size: size);
|
return Icon(AIcons.locationUnlocated, size: size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ class MimeFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
|
|
@ -70,7 +70,7 @@ class MissingFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -60,8 +60,8 @@ class OrFilter extends CollectionFilter {
|
||||||
String getLabel(BuildContext context) => _filters.map((v) => v.getLabel(context)).join(', ');
|
String getLabel(BuildContext context) => _filters.map((v) => v.getLabel(context)).join(', ');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
|
||||||
return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, showGenericIcon: showGenericIcon);
|
return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, allowGenericIcon: allowGenericIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
import 'package:aves/view/view.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class PathFilter extends CollectionFilter {
|
class PathFilter extends CollectionFilter {
|
||||||
static const type = 'path';
|
static const type = 'path';
|
||||||
|
@ -47,6 +51,19 @@ class PathFilter extends CollectionFilter {
|
||||||
@override
|
@override
|
||||||
String get universalLabel => path;
|
String get universalLabel => path;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getLabel(BuildContext context) {
|
||||||
|
final _directory = androidFileUtils.relativeDirectoryFromPath(path);
|
||||||
|
if (_directory == null) return universalLabel;
|
||||||
|
if (_directory.relativeDir.isEmpty) {
|
||||||
|
return _directory.getVolumeDescription(context);
|
||||||
|
}
|
||||||
|
return pContext.split(_directory.relativeDir).last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.explorer, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ class PlaceholderFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -82,7 +82,7 @@ class QueryFilter extends CollectionFilter {
|
||||||
String get universalLabel => query;
|
String get universalLabel => query;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.text, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.text, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ class RatingFilter extends CollectionFilter {
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
|
||||||
return switch (rating) {
|
return switch (rating) {
|
||||||
-1 => Icon(AIcons.ratingRejected, size: size),
|
-1 => Icon(AIcons.ratingRejected, size: size),
|
||||||
0 => Icon(AIcons.ratingUnrated, size: size),
|
0 => Icon(AIcons.ratingUnrated, size: size),
|
||||||
|
|
|
@ -51,7 +51,7 @@ class RecentlyAddedFilter extends CollectionFilter {
|
||||||
String getLabel(BuildContext context) => context.l10n.filterRecentlyAddedLabel;
|
String getLabel(BuildContext context) => context.l10n.filterRecentlyAddedLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.dateRecent, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.dateRecent, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -47,8 +47,8 @@ class TagFilter extends CoveredCollectionFilter {
|
||||||
String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterNoTagLabel : tag;
|
String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterNoTagLabel : tag;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
|
||||||
return showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null;
|
return allowGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -41,7 +41,7 @@ class TrashFilter extends CollectionFilter {
|
||||||
String getLabel(BuildContext context) => context.l10n.filterBinLabel;
|
String getLabel(BuildContext context) => context.l10n.filterBinLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.bin, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.bin, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -99,7 +99,7 @@ class TypeFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
|
|
@ -32,10 +32,10 @@ class MultiPageInfo {
|
||||||
_pages.insert(0, firstPage.copyWith(isDefault: true));
|
_pages.insert(0, firstPage.copyWith(isDefault: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
final burstEntries = mainEntry.burstEntries;
|
final stackedEntries = mainEntry.stackedEntries;
|
||||||
if (burstEntries != null) {
|
if (stackedEntries != null) {
|
||||||
_pageEntries.addEntries(pages.map((pageInfo) {
|
_pageEntries.addEntries(pages.map((pageInfo) {
|
||||||
final pageEntry = burstEntries.firstWhere((entry) => entry.uri == pageInfo.uri);
|
final pageEntry = stackedEntries.firstWhere((entry) => entry.uri == pageInfo.uri);
|
||||||
return MapEntry(pageInfo, pageEntry);
|
return MapEntry(pageInfo, pageEntry);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/filters/recent.dart';
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/naming_pattern.dart';
|
import 'package:aves/model/naming_pattern.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
|
import 'package:aves/widgets/explorer/explorer_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
|
@ -39,6 +40,7 @@ class SettingsDefaults {
|
||||||
AlbumListPage.routeName,
|
AlbumListPage.routeName,
|
||||||
CountryListPage.routeName,
|
CountryListPage.routeName,
|
||||||
TagListPage.routeName,
|
TagListPage.routeName,
|
||||||
|
ExplorerPage.routeName,
|
||||||
];
|
];
|
||||||
|
|
||||||
// collection
|
// collection
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
import 'package:aves/widgets/explorer/explorer_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
@ -12,6 +13,8 @@ extension ExtraHomePageSetting on HomePageSetting {
|
||||||
return AlbumListPage.routeName;
|
return AlbumListPage.routeName;
|
||||||
case HomePageSetting.tags:
|
case HomePageSetting.tags:
|
||||||
return TagListPage.routeName;
|
return TagListPage.routeName;
|
||||||
|
case HomePageSetting.explorer:
|
||||||
|
return ExplorerPage.routeName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
extension ExtraWidgetShape on WidgetShape {
|
extension ExtraWidgetShape on WidgetShape {
|
||||||
Path path(Size widgetSize, double devicePixelRatio) {
|
static const double _defaultCornerRadius = 24;
|
||||||
|
|
||||||
|
Path path(Size widgetSize, double devicePixelRatio, {double? cornerRadiusPx}) {
|
||||||
final rect = Offset.zero & widgetSize;
|
final rect = Offset.zero & widgetSize;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case WidgetShape.rrect:
|
case WidgetShape.rrect:
|
||||||
return Path()..addRRect(BorderRadius.circular(24 * devicePixelRatio).toRRect(rect));
|
return Path()..addRRect(BorderRadius.circular(cornerRadiusPx ?? (_defaultCornerRadius * devicePixelRatio)).toRRect(rect));
|
||||||
case WidgetShape.circle:
|
case WidgetShape.circle:
|
||||||
return Path()
|
return Path()
|
||||||
..addOval(Rect.fromCircle(
|
..addOval(Rect.fromCircle(
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/settings/defaults.dart';
|
import 'package:aves/model/settings/defaults.dart';
|
||||||
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/enums/map_style.dart';
|
import 'package:aves/model/settings/enums/map_style.dart';
|
||||||
import 'package:aves/model/settings/modules/app.dart';
|
import 'package:aves/model/settings/modules/app.dart';
|
||||||
import 'package:aves/model/settings/modules/collection.dart';
|
import 'package:aves/model/settings/modules/collection.dart';
|
||||||
|
@ -206,6 +207,8 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings
|
||||||
|
|
||||||
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
|
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
|
||||||
|
|
||||||
|
bool get animate => accessibilityAnimations.animate;
|
||||||
|
|
||||||
set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString());
|
set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString());
|
||||||
|
|
||||||
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
|
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
|
||||||
|
|
|
@ -3,12 +3,14 @@ import 'dart:collection';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/entry/sort.dart';
|
import 'package:aves/model/entry/sort.dart';
|
||||||
import 'package:aves/model/favourites.dart';
|
import 'package:aves/model/favourites.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/location.dart';
|
import 'package:aves/model/filters/location.dart';
|
||||||
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
|
@ -18,6 +20,7 @@ import 'package:aves/model/source/events.dart';
|
||||||
import 'package:aves/model/source/location/location.dart';
|
import 'package:aves/model/source/location/location.dart';
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
|
@ -34,7 +37,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
int? id;
|
int? id;
|
||||||
bool listenToSource, groupBursts, fixedSort;
|
bool listenToSource, stackBursts, stackDevelopedRaws, fixedSort;
|
||||||
List<AvesEntry>? fixedSelection;
|
List<AvesEntry>? fixedSelection;
|
||||||
|
|
||||||
final Set<AvesEntry> _syntheticEntries = {};
|
final Set<AvesEntry> _syntheticEntries = {};
|
||||||
|
@ -47,7 +50,8 @@ class CollectionLens with ChangeNotifier {
|
||||||
Set<CollectionFilter?>? filters,
|
Set<CollectionFilter?>? filters,
|
||||||
this.id,
|
this.id,
|
||||||
this.listenToSource = true,
|
this.listenToSource = true,
|
||||||
this.groupBursts = true,
|
this.stackBursts = true,
|
||||||
|
this.stackDevelopedRaws = true,
|
||||||
this.fixedSort = false,
|
this.fixedSort = false,
|
||||||
this.fixedSelection,
|
this.fixedSelection,
|
||||||
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
||||||
|
@ -192,30 +196,59 @@ class CollectionLens with ChangeNotifier {
|
||||||
_disposeSyntheticEntries();
|
_disposeSyntheticEntries();
|
||||||
_filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry))));
|
_filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry))));
|
||||||
|
|
||||||
if (groupBursts) {
|
if (stackBursts) {
|
||||||
_groupBursts();
|
_stackBursts();
|
||||||
|
}
|
||||||
|
if (stackDevelopedRaws) {
|
||||||
|
_stackDevelopedRaws();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _groupBursts() {
|
void _stackBursts() {
|
||||||
final byBurstKey = groupBy<AvesEntry, String?>(_filteredSortedEntries, (entry) => entry.getBurstKey(burstPatterns)).whereNotNullKey();
|
final byBurstKey = groupBy<AvesEntry, String?>(_filteredSortedEntries, (entry) => entry.getBurstKey(burstPatterns)).whereNotNullKey();
|
||||||
byBurstKey.forEach((burstKey, entries) {
|
byBurstKey.forEach((burstKey, entries) {
|
||||||
if (entries.length > 1) {
|
if (entries.length > 1) {
|
||||||
entries.sort(AvesEntrySort.compareByName);
|
entries.sort(AvesEntrySort.compareByName);
|
||||||
final mainEntry = entries.first;
|
final mainEntry = entries.first;
|
||||||
final burstEntry = mainEntry.copyWith(burstEntries: entries);
|
final stackEntry = mainEntry.copyWith(stackedEntries: entries);
|
||||||
_syntheticEntries.add(burstEntry);
|
_syntheticEntries.add(stackEntry);
|
||||||
|
|
||||||
entries.skip(1).toList().forEach((subEntry) {
|
entries.skip(1).forEach((subEntry) {
|
||||||
_filteredSortedEntries.remove(subEntry);
|
_filteredSortedEntries.remove(subEntry);
|
||||||
});
|
});
|
||||||
final index = _filteredSortedEntries.indexOf(mainEntry);
|
final index = _filteredSortedEntries.indexOf(mainEntry);
|
||||||
_filteredSortedEntries.removeAt(index);
|
_filteredSortedEntries.removeAt(index);
|
||||||
_filteredSortedEntries.insert(index, burstEntry);
|
_filteredSortedEntries.insert(index, stackEntry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _stackDevelopedRaws() {
|
||||||
|
final allRawEntries = _filteredSortedEntries.where((entry) => entry.isRaw).toSet();
|
||||||
|
if (allRawEntries.isNotEmpty) {
|
||||||
|
final allDevelopedEntries = _filteredSortedEntries.where(MimeFilter(MimeTypes.jpeg).test).toSet();
|
||||||
|
final rawEntriesByDir = groupBy<AvesEntry, String?>(allRawEntries, (entry) => entry.directory);
|
||||||
|
rawEntriesByDir.forEach((dir, dirRawEntries) {
|
||||||
|
if (dir != null) {
|
||||||
|
final dirDevelopedEntries = allDevelopedEntries.where((entry) => entry.directory == dir).toSet();
|
||||||
|
for (final rawEntry in dirRawEntries) {
|
||||||
|
final rawFilename = rawEntry.filenameWithoutExtension;
|
||||||
|
final developedEntry = dirDevelopedEntries.firstWhereOrNull((entry) => entry.filenameWithoutExtension == rawFilename);
|
||||||
|
if (developedEntry != null) {
|
||||||
|
final stackEntry = rawEntry.copyWith(stackedEntries: [rawEntry, developedEntry]);
|
||||||
|
_syntheticEntries.add(stackEntry);
|
||||||
|
|
||||||
|
_filteredSortedEntries.remove(developedEntry);
|
||||||
|
final index = _filteredSortedEntries.indexOf(rawEntry);
|
||||||
|
_filteredSortedEntries.removeAt(index);
|
||||||
|
_filteredSortedEntries.insert(0, stackEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _applySort() {
|
void _applySort() {
|
||||||
if (fixedSort) return;
|
if (fixedSort) return;
|
||||||
|
|
||||||
|
@ -322,23 +355,52 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEntryRemoved(Set<AvesEntry> entries) {
|
void _onEntryRemoved(Set<AvesEntry> entries) {
|
||||||
if (groupBursts) {
|
if (_syntheticEntries.isNotEmpty) {
|
||||||
// find impacted burst groups
|
// find impacted stacks
|
||||||
final obsoleteBurstEntries = <AvesEntry>{};
|
final obsoleteStacks = <AvesEntry>{};
|
||||||
final burstKeys = entries.map((entry) => entry.getBurstKey(burstPatterns)).whereNotNull().toSet();
|
|
||||||
if (burstKeys.isNotEmpty) {
|
void _replaceStack(AvesEntry stackEntry, AvesEntry entry) {
|
||||||
_filteredSortedEntries.where((entry) => entry.isBurst && burstKeys.contains(entry.getBurstKey(burstPatterns))).forEach((mainEntry) {
|
obsoleteStacks.add(stackEntry);
|
||||||
final subEntries = mainEntry.burstEntries!;
|
fixedSelection?.replace(stackEntry, entry);
|
||||||
|
_filteredSortedEntries.replace(stackEntry, entry);
|
||||||
|
_sortedEntries?.replace(stackEntry, entry);
|
||||||
|
sections.forEach((key, sectionEntries) => sectionEntries.replace(stackEntry, entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
final stacks = _filteredSortedEntries.where((entry) => entry.isStack).toSet();
|
||||||
|
stacks.forEach((stackEntry) {
|
||||||
|
final subEntries = stackEntry.stackedEntries!;
|
||||||
|
if (subEntries.any(entries.contains)) {
|
||||||
|
final mainEntry = subEntries.first;
|
||||||
|
|
||||||
// remove the deleted sub-entries
|
// remove the deleted sub-entries
|
||||||
subEntries.removeWhere(entries.contains);
|
subEntries.removeWhere(entries.contains);
|
||||||
if (subEntries.isEmpty) {
|
|
||||||
// remove the burst entry itself
|
switch (subEntries.length) {
|
||||||
obsoleteBurstEntries.add(mainEntry);
|
case 0:
|
||||||
|
// remove the stack itself
|
||||||
|
obsoleteStacks.add(stackEntry);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// replace the stack by the last remaining sub-entry
|
||||||
|
_replaceStack(stackEntry, subEntries.first);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// keep the stack with the remaining sub-entries
|
||||||
|
if (!subEntries.contains(mainEntry)) {
|
||||||
|
// recreate the stack with the correct main entry
|
||||||
|
_replaceStack(stackEntry, subEntries.first.copyWith(stackedEntries: subEntries));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// TODO TLAD [burst] recreate the burst main entry if the first sub-entry got deleted
|
}
|
||||||
});
|
});
|
||||||
entries.addAll(obsoleteBurstEntries);
|
|
||||||
}
|
obsoleteStacks.forEach((stackEntry) {
|
||||||
|
_syntheticEntries.remove(stackEntry);
|
||||||
|
stackEntry.dispose();
|
||||||
|
});
|
||||||
|
entries.addAll(obsoleteStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should remove obsolete entries and sections
|
// we should remove obsolete entries and sections
|
||||||
|
|
|
@ -134,4 +134,15 @@ class MimeTypes {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const Map<String, String> _defaultExtensions = {
|
||||||
|
bmp: '.bmp',
|
||||||
|
gif: '.gif',
|
||||||
|
jpeg: '.jpg',
|
||||||
|
png: '.png',
|
||||||
|
svg: '.svg',
|
||||||
|
webp: '.webp',
|
||||||
|
};
|
||||||
|
|
||||||
|
static String? extensionFor(String mimeType) => _defaultExtensions[mimeType];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/services/app_service.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -27,7 +28,11 @@ class IntentService {
|
||||||
'uris': uris,
|
'uris': uris,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
if (e.code == 'submitPickedItems-large') {
|
||||||
|
throw TooManyItemsException();
|
||||||
|
} else {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,15 +193,17 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class EntryConvertOptions extends Equatable {
|
class EntryConvertOptions extends Equatable {
|
||||||
|
final EntryConvertAction action;
|
||||||
final String mimeType;
|
final String mimeType;
|
||||||
final bool writeMetadata;
|
final bool writeMetadata;
|
||||||
final LengthUnit lengthUnit;
|
final LengthUnit lengthUnit;
|
||||||
final int width, height, quality;
|
final int width, height, quality;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [mimeType, writeMetadata, lengthUnit, width, height, quality];
|
List<Object?> get props => [action, mimeType, writeMetadata, lengthUnit, width, height, quality];
|
||||||
|
|
||||||
const EntryConvertOptions({
|
const EntryConvertOptions({
|
||||||
|
required this.action,
|
||||||
required this.mimeType,
|
required this.mimeType,
|
||||||
required this.writeMetadata,
|
required this.writeMetadata,
|
||||||
required this.lengthUnit,
|
required this.lengthUnit,
|
||||||
|
|
|
@ -33,6 +33,8 @@ abstract class StorageService {
|
||||||
|
|
||||||
Future<bool> deleteTempDirectory();
|
Future<bool> deleteTempDirectory();
|
||||||
|
|
||||||
|
Future<bool> deleteExternalCache();
|
||||||
|
|
||||||
// returns whether user granted access to a directory of his choosing
|
// returns whether user granted access to a directory of his choosing
|
||||||
Future<bool> requestDirectoryAccess(String path);
|
Future<bool> requestDirectoryAccess(String path);
|
||||||
|
|
||||||
|
@ -202,6 +204,17 @@ class PlatformStorageService implements StorageService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> deleteExternalCache() async {
|
||||||
|
try {
|
||||||
|
final result = await _platform.invokeMethod('deleteExternalCache');
|
||||||
|
if (result != null) return result as bool;
|
||||||
|
} on PlatformException catch (e, stack) {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> canRequestMediaFileBulkAccess() async {
|
Future<bool> canRequestMediaFileBulkAccess() async {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -60,7 +60,7 @@ class ADurations {
|
||||||
static const highlightJumpDelay = Duration(milliseconds: 400);
|
static const highlightJumpDelay = Duration(milliseconds: 400);
|
||||||
static const highlightScrollInitDelay = Duration(milliseconds: 800);
|
static const highlightScrollInitDelay = Duration(milliseconds: 800);
|
||||||
static const motionPhotoAutoPlayDelay = Duration(milliseconds: 700);
|
static const motionPhotoAutoPlayDelay = Duration(milliseconds: 700);
|
||||||
static const videoPauseAppInactiveDelay = Duration(milliseconds: 300);
|
static const appInactiveReactionDelay = Duration(milliseconds: 300);
|
||||||
static const videoOverlayHideDelay = Duration(milliseconds: 500);
|
static const videoOverlayHideDelay = Duration(milliseconds: 500);
|
||||||
static const videoProgressTimerInterval = Duration(milliseconds: 300);
|
static const videoProgressTimerInterval = Duration(milliseconds: 300);
|
||||||
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
|
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
|
||||||
|
|
|
@ -29,8 +29,10 @@ class AIcons {
|
||||||
static const disc = Icons.fiber_manual_record;
|
static const disc = Icons.fiber_manual_record;
|
||||||
static const display = Icons.light_mode_outlined;
|
static const display = Icons.light_mode_outlined;
|
||||||
static const error = Icons.error_outline;
|
static const error = Icons.error_outline;
|
||||||
|
static const explorer = Icons.account_tree_outlined;
|
||||||
static const folder = Icons.folder_outlined;
|
static const folder = Icons.folder_outlined;
|
||||||
static const grid = Icons.grid_on_outlined;
|
static const geoBounds = Icons.public_outlined;
|
||||||
|
static final github = MdiIcons.github;
|
||||||
static const home = Icons.home_outlined;
|
static const home = Icons.home_outlined;
|
||||||
|
|
||||||
// as of Flutter v3.16.3,
|
// as of Flutter v3.16.3,
|
||||||
|
@ -39,13 +41,15 @@ class AIcons {
|
||||||
static const important = IconData(labelImportantOutlineCodePoint, fontFamily: materialIconsFontFamily, matchTextDirection: true);
|
static const important = IconData(labelImportantOutlineCodePoint, fontFamily: materialIconsFontFamily, matchTextDirection: true);
|
||||||
|
|
||||||
static const language = Icons.translate_outlined;
|
static const language = Icons.translate_outlined;
|
||||||
|
static final legal = MdiIcons.scaleBalance;
|
||||||
static const location = Icons.place_outlined;
|
static const location = Icons.place_outlined;
|
||||||
static const locationUnlocated = Icons.location_off_outlined;
|
static const locationUnlocated = Icons.location_off_outlined;
|
||||||
static const country = Icons.flag_outlined;
|
static const country = Icons.flag_outlined;
|
||||||
static const state = Icons.flag_outlined;
|
static const state = Icons.flag_outlined;
|
||||||
static const place = Icons.place_outlined;
|
static const place = Icons.place_outlined;
|
||||||
static const mainStorage = Icons.smartphone_outlined;
|
|
||||||
static const mimeType = Icons.code_outlined;
|
static const mimeType = Icons.code_outlined;
|
||||||
|
static const name = Icons.abc_outlined;
|
||||||
|
static const newTier = Icons.fiber_new_outlined;
|
||||||
static const opacity = Icons.opacity;
|
static const opacity = Icons.opacity;
|
||||||
static const palette = Icons.palette_outlined;
|
static const palette = Icons.palette_outlined;
|
||||||
static final privacy = MdiIcons.shieldAccountOutline;
|
static final privacy = MdiIcons.shieldAccountOutline;
|
||||||
|
@ -54,15 +58,20 @@ class AIcons {
|
||||||
static final ratingRejected = MdiIcons.starMinusOutline;
|
static final ratingRejected = MdiIcons.starMinusOutline;
|
||||||
static final ratingUnrated = MdiIcons.starOffOutline;
|
static final ratingUnrated = MdiIcons.starOffOutline;
|
||||||
static const raw = Icons.raw_on_outlined;
|
static const raw = Icons.raw_on_outlined;
|
||||||
static const shooting = Icons.camera_outlined;
|
|
||||||
static const removableStorage = Icons.sd_storage_outlined;
|
|
||||||
static const sensorControlEnabled = Icons.explore_outlined;
|
static const sensorControlEnabled = Icons.explore_outlined;
|
||||||
static const sensorControlDisabled = Icons.explore_off_outlined;
|
static const sensorControlDisabled = Icons.explore_off_outlined;
|
||||||
static const settings = Icons.settings_outlined;
|
static const settings = Icons.settings_outlined;
|
||||||
|
static const shooting = Icons.camera_outlined;
|
||||||
static const size = Icons.data_usage_outlined;
|
static const size = Icons.data_usage_outlined;
|
||||||
static const text = Icons.format_quote_outlined;
|
static const storageCard = Icons.sd_storage_outlined;
|
||||||
|
static const storageMain = Icons.smartphone_outlined;
|
||||||
|
static const streamVideo = Icons.movie_outlined;
|
||||||
|
static const streamAudio = Icons.audiotrack_outlined;
|
||||||
|
static const streamText = Icons.closed_caption_outlined;
|
||||||
static const tag = Icons.local_offer_outlined;
|
static const tag = Icons.local_offer_outlined;
|
||||||
static final tagUntagged = MdiIcons.tagOffOutline;
|
static final tagUntagged = MdiIcons.tagOffOutline;
|
||||||
|
static const text = Icons.format_quote_outlined;
|
||||||
|
static const thumbnails = Icons.grid_on_outlined;
|
||||||
static const volumeMin = Icons.volume_mute_outlined;
|
static const volumeMin = Icons.volume_mute_outlined;
|
||||||
static const volumeMax = Icons.volume_up_outlined;
|
static const volumeMax = Icons.volume_up_outlined;
|
||||||
|
|
||||||
|
@ -100,7 +109,6 @@ class AIcons {
|
||||||
static const favouriteActive = Icons.favorite;
|
static const favouriteActive = Icons.favorite;
|
||||||
static final filter = MdiIcons.filterOutline;
|
static final filter = MdiIcons.filterOutline;
|
||||||
static final filterOff = MdiIcons.filterOffOutline;
|
static final filterOff = MdiIcons.filterOffOutline;
|
||||||
static const geoBounds = Icons.public_outlined;
|
|
||||||
static const goUp = Icons.arrow_upward_outlined;
|
static const goUp = Icons.arrow_upward_outlined;
|
||||||
static const hide = Icons.visibility_off_outlined;
|
static const hide = Icons.visibility_off_outlined;
|
||||||
static const info = Icons.info_outlined;
|
static const info = Icons.info_outlined;
|
||||||
|
@ -110,8 +118,7 @@ class AIcons {
|
||||||
static final move = MdiIcons.fileMoveOutline;
|
static final move = MdiIcons.fileMoveOutline;
|
||||||
static const mute = Icons.volume_off_outlined;
|
static const mute = Icons.volume_off_outlined;
|
||||||
static const unmute = Icons.volume_up_outlined;
|
static const unmute = Icons.volume_up_outlined;
|
||||||
static const name = Icons.abc_outlined;
|
static const rename = Icons.abc_outlined;
|
||||||
static const newTier = Icons.fiber_new_outlined;
|
|
||||||
static const openOutside = Icons.open_in_new_outlined;
|
static const openOutside = Icons.open_in_new_outlined;
|
||||||
static final openVideo = MdiIcons.moviePlayOutline;
|
static final openVideo = MdiIcons.moviePlayOutline;
|
||||||
static const pin = Icons.push_pin_outlined;
|
static const pin = Icons.push_pin_outlined;
|
||||||
|
@ -133,20 +140,17 @@ class AIcons {
|
||||||
static const rotateScreen = Icons.screen_rotation_outlined;
|
static const rotateScreen = Icons.screen_rotation_outlined;
|
||||||
static const search = Icons.search_outlined;
|
static const search = Icons.search_outlined;
|
||||||
static const select = Icons.select_all_outlined;
|
static const select = Icons.select_all_outlined;
|
||||||
|
static const selectStreams = Icons.translate_outlined;
|
||||||
static const setAs = Icons.wallpaper_outlined;
|
static const setAs = Icons.wallpaper_outlined;
|
||||||
|
static final setBoundEnd = MdiIcons.rayEnd;
|
||||||
|
static final setBoundStart = MdiIcons.rayStart;
|
||||||
static final setCover = MdiIcons.imageEditOutline;
|
static final setCover = MdiIcons.imageEditOutline;
|
||||||
static final setEnd = MdiIcons.rayEnd;
|
|
||||||
static final setStart = MdiIcons.rayStart;
|
|
||||||
static const share = Icons.share_outlined;
|
static const share = Icons.share_outlined;
|
||||||
static const show = Icons.visibility_outlined;
|
static const show = Icons.visibility_outlined;
|
||||||
static final showFullscreen = MdiIcons.arrowExpand;
|
static final showFullscreen = MdiIcons.arrowExpand;
|
||||||
static const slideshow = Icons.slideshow_outlined;
|
static const slideshow = Icons.slideshow_outlined;
|
||||||
static const speed = Icons.speed_outlined;
|
static const speed = Icons.speed_outlined;
|
||||||
static const stats = Icons.donut_small_outlined;
|
static const stats = Icons.donut_small_outlined;
|
||||||
static const streams = Icons.translate_outlined;
|
|
||||||
static const streamVideo = Icons.movie_outlined;
|
|
||||||
static const streamAudio = Icons.audiotrack_outlined;
|
|
||||||
static const streamText = Icons.closed_caption_outlined;
|
|
||||||
static const vaultLock = Icons.lock_outline;
|
static const vaultLock = Icons.lock_outline;
|
||||||
static const vaultAdd = Icons.enhanced_encryption_outlined;
|
static const vaultAdd = Icons.enhanced_encryption_outlined;
|
||||||
static final vaultConfigure = MdiIcons.shieldLockOutline;
|
static final vaultConfigure = MdiIcons.shieldLockOutline;
|
||||||
|
@ -190,9 +194,6 @@ class AIcons {
|
||||||
static const selected = Icons.check_circle_outline;
|
static const selected = Icons.check_circle_outline;
|
||||||
static const unselected = Icons.radio_button_unchecked;
|
static const unselected = Icons.radio_button_unchecked;
|
||||||
|
|
||||||
static final github = MdiIcons.github;
|
|
||||||
static final legal = MdiIcons.scaleBalance;
|
|
||||||
|
|
||||||
// Material Icons references to make constant instances of `IconData`
|
// Material Icons references to make constant instances of `IconData`
|
||||||
// as non-constant instances of `IconData` prevent icon font tree shaking
|
// as non-constant instances of `IconData` prevent icon font tree shaking
|
||||||
static const labelImportantOutlineCodePoint = 0xe362;
|
static const labelImportantOutlineCodePoint = 0xe362;
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
extension ExtraList<E> on List<E> {
|
||||||
|
bool replace(E old, E newItem) {
|
||||||
|
final index = indexOf(old);
|
||||||
|
if (index == -1) return false;
|
||||||
|
|
||||||
|
this[index] = newItem;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ExtraMapNullableKey<K extends Object, V> on Map<K?, V> {
|
extension ExtraMapNullableKey<K extends Object, V> on Map<K?, V> {
|
||||||
Map<K, V> whereNotNullKey() => <K, V>{for (var v in keys.whereNotNull()) v: this[v] as V};
|
Map<K, V> whereNotNullKey() => <K, V>{for (var v in keys.whereNotNull()) v: this[v] as V};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ extension ExtraChipActionView on ChipAction {
|
||||||
ChipAction.goToCountryPage => l10n.chipActionGoToCountryPage,
|
ChipAction.goToCountryPage => l10n.chipActionGoToCountryPage,
|
||||||
ChipAction.goToPlacePage => l10n.chipActionGoToPlacePage,
|
ChipAction.goToPlacePage => l10n.chipActionGoToPlacePage,
|
||||||
ChipAction.goToTagPage => l10n.chipActionGoToTagPage,
|
ChipAction.goToTagPage => l10n.chipActionGoToTagPage,
|
||||||
|
ChipAction.goToExplorerPage => l10n.chipActionGoToExplorerPage,
|
||||||
ChipAction.ratingOrGreater ||
|
ChipAction.ratingOrGreater ||
|
||||||
ChipAction.ratingOrLower =>
|
ChipAction.ratingOrLower =>
|
||||||
// different data depending on state
|
// different data depending on state
|
||||||
|
@ -30,6 +31,7 @@ extension ExtraChipActionView on ChipAction {
|
||||||
ChipAction.goToCountryPage => AIcons.country,
|
ChipAction.goToCountryPage => AIcons.country,
|
||||||
ChipAction.goToPlacePage => AIcons.place,
|
ChipAction.goToPlacePage => AIcons.place,
|
||||||
ChipAction.goToTagPage => AIcons.tag,
|
ChipAction.goToTagPage => AIcons.tag,
|
||||||
|
ChipAction.goToExplorerPage => AIcons.explorer,
|
||||||
ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating,
|
ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating,
|
||||||
ChipAction.reverse => AIcons.reverse,
|
ChipAction.reverse => AIcons.reverse,
|
||||||
ChipAction.hide => AIcons.hide,
|
ChipAction.hide => AIcons.hide,
|
||||||
|
|
|
@ -67,7 +67,7 @@ extension ExtraChipSetActionView on ChipSetAction {
|
||||||
ChipSetAction.showCountryStates => AIcons.state,
|
ChipSetAction.showCountryStates => AIcons.state,
|
||||||
ChipSetAction.showCollection => AIcons.allCollection,
|
ChipSetAction.showCollection => AIcons.allCollection,
|
||||||
// selecting (single filter)
|
// selecting (single filter)
|
||||||
ChipSetAction.rename => AIcons.name,
|
ChipSetAction.rename => AIcons.rename,
|
||||||
ChipSetAction.setCover => AIcons.setCover,
|
ChipSetAction.setCover => AIcons.setCover,
|
||||||
ChipSetAction.configureVault => AIcons.vaultConfigure,
|
ChipSetAction.configureVault => AIcons.vaultConfigure,
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,7 +90,7 @@ extension ExtraEntryActionView on EntryAction {
|
||||||
EntryAction.restore => AIcons.restore,
|
EntryAction.restore => AIcons.restore,
|
||||||
EntryAction.convert => AIcons.convert,
|
EntryAction.convert => AIcons.convert,
|
||||||
EntryAction.print => AIcons.print,
|
EntryAction.print => AIcons.print,
|
||||||
EntryAction.rename => AIcons.name,
|
EntryAction.rename => AIcons.rename,
|
||||||
EntryAction.copy => AIcons.copy,
|
EntryAction.copy => AIcons.copy,
|
||||||
EntryAction.move => AIcons.move,
|
EntryAction.move => AIcons.move,
|
||||||
EntryAction.share => AIcons.share,
|
EntryAction.share => AIcons.share,
|
||||||
|
@ -109,7 +109,7 @@ extension ExtraEntryActionView on EntryAction {
|
||||||
EntryAction.videoToggleMute =>
|
EntryAction.videoToggleMute =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
AIcons.mute,
|
AIcons.mute,
|
||||||
EntryAction.videoSelectStreams => AIcons.streams,
|
EntryAction.videoSelectStreams => AIcons.selectStreams,
|
||||||
EntryAction.videoSetSpeed => AIcons.speed,
|
EntryAction.videoSetSpeed => AIcons.speed,
|
||||||
EntryAction.videoABRepeat => AIcons.repeat,
|
EntryAction.videoABRepeat => AIcons.repeat,
|
||||||
EntryAction.videoSettings => AIcons.videoSettings,
|
EntryAction.videoSettings => AIcons.videoSettings,
|
||||||
|
|
|
@ -5,45 +5,46 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
extension ExtraEntrySetActionView on EntrySetAction {
|
extension ExtraEntrySetActionView on EntrySetAction {
|
||||||
String getText(BuildContext context) {
|
String getText(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
// general
|
// general
|
||||||
EntrySetAction.configureView => context.l10n.menuActionConfigureView,
|
EntrySetAction.configureView => l10n.menuActionConfigureView,
|
||||||
EntrySetAction.select => context.l10n.menuActionSelect,
|
EntrySetAction.select => l10n.menuActionSelect,
|
||||||
EntrySetAction.selectAll => context.l10n.menuActionSelectAll,
|
EntrySetAction.selectAll => l10n.menuActionSelectAll,
|
||||||
EntrySetAction.selectNone => context.l10n.menuActionSelectNone,
|
EntrySetAction.selectNone => l10n.menuActionSelectNone,
|
||||||
// browsing
|
// browsing
|
||||||
EntrySetAction.searchCollection => MaterialLocalizations.of(context).searchFieldLabel,
|
EntrySetAction.searchCollection => MaterialLocalizations.of(context).searchFieldLabel,
|
||||||
EntrySetAction.toggleTitleSearch =>
|
EntrySetAction.toggleTitleSearch =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
context.l10n.collectionActionShowTitleSearch,
|
l10n.collectionActionShowTitleSearch,
|
||||||
EntrySetAction.addShortcut => context.l10n.collectionActionAddShortcut,
|
EntrySetAction.addShortcut => l10n.collectionActionAddShortcut,
|
||||||
EntrySetAction.setHome => context.l10n.collectionActionSetHome,
|
EntrySetAction.setHome => l10n.collectionActionSetHome,
|
||||||
EntrySetAction.emptyBin => context.l10n.collectionActionEmptyBin,
|
EntrySetAction.emptyBin => l10n.collectionActionEmptyBin,
|
||||||
// browsing or selecting
|
// browsing or selecting
|
||||||
EntrySetAction.map => context.l10n.menuActionMap,
|
EntrySetAction.map => l10n.menuActionMap,
|
||||||
EntrySetAction.slideshow => context.l10n.menuActionSlideshow,
|
EntrySetAction.slideshow => l10n.menuActionSlideshow,
|
||||||
EntrySetAction.stats => context.l10n.menuActionStats,
|
EntrySetAction.stats => l10n.menuActionStats,
|
||||||
EntrySetAction.rescan => context.l10n.collectionActionRescan,
|
EntrySetAction.rescan => l10n.collectionActionRescan,
|
||||||
// selecting
|
// selecting
|
||||||
EntrySetAction.share => context.l10n.entryActionShare,
|
EntrySetAction.share => l10n.entryActionShare,
|
||||||
EntrySetAction.delete => context.l10n.entryActionDelete,
|
EntrySetAction.delete => l10n.entryActionDelete,
|
||||||
EntrySetAction.restore => context.l10n.entryActionRestore,
|
EntrySetAction.restore => l10n.entryActionRestore,
|
||||||
EntrySetAction.copy => context.l10n.collectionActionCopy,
|
EntrySetAction.copy => l10n.collectionActionCopy,
|
||||||
EntrySetAction.move => context.l10n.collectionActionMove,
|
EntrySetAction.move => l10n.collectionActionMove,
|
||||||
EntrySetAction.rename => context.l10n.entryActionRename,
|
EntrySetAction.rename => l10n.entryActionRename,
|
||||||
EntrySetAction.convert => context.l10n.entryActionConvert,
|
EntrySetAction.convert => l10n.entryActionConvert,
|
||||||
EntrySetAction.toggleFavourite =>
|
EntrySetAction.toggleFavourite =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
context.l10n.entryActionAddFavourite,
|
l10n.entryActionAddFavourite,
|
||||||
EntrySetAction.rotateCCW => context.l10n.entryActionRotateCCW,
|
EntrySetAction.rotateCCW => l10n.entryActionRotateCCW,
|
||||||
EntrySetAction.rotateCW => context.l10n.entryActionRotateCW,
|
EntrySetAction.rotateCW => l10n.entryActionRotateCW,
|
||||||
EntrySetAction.flip => context.l10n.entryActionFlip,
|
EntrySetAction.flip => l10n.entryActionFlip,
|
||||||
EntrySetAction.editDate => context.l10n.entryInfoActionEditDate,
|
EntrySetAction.editDate => l10n.entryInfoActionEditDate,
|
||||||
EntrySetAction.editLocation => context.l10n.entryInfoActionEditLocation,
|
EntrySetAction.editLocation => l10n.entryInfoActionEditLocation,
|
||||||
EntrySetAction.editTitleDescription => context.l10n.entryInfoActionEditTitleDescription,
|
EntrySetAction.editTitleDescription => l10n.entryInfoActionEditTitleDescription,
|
||||||
EntrySetAction.editRating => context.l10n.entryInfoActionEditRating,
|
EntrySetAction.editRating => l10n.entryInfoActionEditRating,
|
||||||
EntrySetAction.editTags => context.l10n.entryInfoActionEditTags,
|
EntrySetAction.editTags => l10n.entryInfoActionEditTags,
|
||||||
EntrySetAction.removeMetadata => context.l10n.entryInfoActionRemoveMetadata,
|
EntrySetAction.removeMetadata => l10n.entryInfoActionRemoveMetadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ extension ExtraEntrySetActionView on EntrySetAction {
|
||||||
EntrySetAction.restore => AIcons.restore,
|
EntrySetAction.restore => AIcons.restore,
|
||||||
EntrySetAction.copy => AIcons.copy,
|
EntrySetAction.copy => AIcons.copy,
|
||||||
EntrySetAction.move => AIcons.move,
|
EntrySetAction.move => AIcons.move,
|
||||||
EntrySetAction.rename => AIcons.name,
|
EntrySetAction.rename => AIcons.rename,
|
||||||
EntrySetAction.convert => AIcons.convert,
|
EntrySetAction.convert => AIcons.convert,
|
||||||
EntrySetAction.toggleFavourite =>
|
EntrySetAction.toggleFavourite =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
|
|
21
lib/view/src/metadata/convert_action.dart
Normal file
21
lib/view/src/metadata/convert_action.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
extension ExtraEntryConvertActionView on EntryConvertAction {
|
||||||
|
String getText(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return switch (this) {
|
||||||
|
EntryConvertAction.convert => l10n.entryActionConvert,
|
||||||
|
EntryConvertAction.convertMotionPhotoToStillImage => l10n.entryActionConvertMotionPhotoToStillImage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData getIconData() {
|
||||||
|
return switch (this) {
|
||||||
|
EntryConvertAction.convert => AIcons.convert,
|
||||||
|
EntryConvertAction.convertMotionPhotoToStillImage => AIcons.convertToStillImage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,6 +83,7 @@ extension ExtraHomePageSettingView on HomePageSetting {
|
||||||
HomePageSetting.collection => l10n.drawerCollectionAll,
|
HomePageSetting.collection => l10n.drawerCollectionAll,
|
||||||
HomePageSetting.albums => l10n.drawerAlbumPage,
|
HomePageSetting.albums => l10n.drawerAlbumPage,
|
||||||
HomePageSetting.tags => l10n.drawerTagPage,
|
HomePageSetting.tags => l10n.drawerTagPage,
|
||||||
|
HomePageSetting.explorer => l10n.explorerPageTitle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ extension ExtraAlbumChipGroupFactorView on AlbumChipGroupFactor {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
AlbumChipGroupFactor.importance => AIcons.important,
|
AlbumChipGroupFactor.importance => AIcons.important,
|
||||||
AlbumChipGroupFactor.mimeType => AIcons.mimeType,
|
AlbumChipGroupFactor.mimeType => AIcons.mimeType,
|
||||||
AlbumChipGroupFactor.volume => AIcons.removableStorage,
|
AlbumChipGroupFactor.volume => AIcons.storageCard,
|
||||||
AlbumChipGroupFactor.none => AIcons.clear,
|
AlbumChipGroupFactor.none => AIcons.clear,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ export 'src/actions/map_cluster.dart';
|
||||||
export 'src/actions/share.dart';
|
export 'src/actions/share.dart';
|
||||||
export 'src/actions/slideshow.dart';
|
export 'src/actions/slideshow.dart';
|
||||||
export 'src/editor/enums.dart';
|
export 'src/editor/enums.dart';
|
||||||
|
export 'src/metadata/convert_action.dart';
|
||||||
export 'src/metadata/date_edit_action.dart';
|
export 'src/metadata/date_edit_action.dart';
|
||||||
export 'src/metadata/date_field_source.dart';
|
export 'src/metadata/date_field_source.dart';
|
||||||
export 'src/metadata/fields.dart';
|
export 'src/metadata/fields.dart';
|
||||||
|
|
|
@ -38,8 +38,10 @@ void widgetMainCommon(AppFlavor flavor) async {
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _drawWidget(dynamic args) async {
|
Future<Map<String, dynamic>> _drawWidget(dynamic args) async {
|
||||||
final widgetId = args['widgetId'] as int;
|
final widgetId = args['widgetId'] as int;
|
||||||
final widthPx = args['widthPx'] as int;
|
final sizesDip = (args['sizesDip'] as List).cast<Map>().map((kv) {
|
||||||
final heightPx = args['heightPx'] as int;
|
return Size(kv['widthDip'] as double, kv['heightDip'] as double);
|
||||||
|
}).toList();
|
||||||
|
final cornerRadiusPx = args['cornerRadiusPx'] as double?;
|
||||||
final devicePixelRatio = args['devicePixelRatio'] as double;
|
final devicePixelRatio = args['devicePixelRatio'] as double;
|
||||||
final drawEntryImage = args['drawEntryImage'] as bool;
|
final drawEntryImage = args['drawEntryImage'] as bool;
|
||||||
final reuseEntry = args['reuseEntry'] as bool;
|
final reuseEntry = args['reuseEntry'] as bool;
|
||||||
|
@ -53,14 +55,22 @@ Future<Map<String, dynamic>> _drawWidget(dynamic args) async {
|
||||||
entry: entry,
|
entry: entry,
|
||||||
devicePixelRatio: devicePixelRatio,
|
devicePixelRatio: devicePixelRatio,
|
||||||
);
|
);
|
||||||
final bytes = await painter.drawWidget(
|
final bytesBySizeDip = <Map<String, dynamic>>[];
|
||||||
widthPx: widthPx,
|
await Future.forEach(sizesDip, (sizeDip) async {
|
||||||
heightPx: heightPx,
|
final bytes = await painter.drawWidget(
|
||||||
outline: outline,
|
sizeDip: sizeDip,
|
||||||
shape: settings.getWidgetShape(widgetId),
|
cornerRadiusPx: cornerRadiusPx,
|
||||||
);
|
outline: outline,
|
||||||
|
shape: settings.getWidgetShape(widgetId),
|
||||||
|
);
|
||||||
|
bytesBySizeDip.add({
|
||||||
|
'widthDip': sizeDip.width,
|
||||||
|
'heightDip': sizeDip.height,
|
||||||
|
'bytes': bytes,
|
||||||
|
});
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
'bytes': bytes,
|
'bytesBySizeDip': bytesBySizeDip,
|
||||||
'updateOnTap': settings.getWidgetOpenPage(widgetId) == WidgetOpenPage.updateWidget,
|
'updateOnTap': settings.getWidgetOpenPage(widgetId) == WidgetOpenPage.updateWidget,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ class _AboutDataUsageState extends State<AboutDataUsage> with FeedbackMixin {
|
||||||
label: context.l10n.aboutDataUsageClearCache,
|
label: context.l10n.aboutDataUsageClearCache,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await storageService.deleteTempDirectory();
|
await storageService.deleteTempDirectory();
|
||||||
|
await storageService.deleteExternalCache();
|
||||||
await mediaFetchService.clearSizedThumbnailDiskCache();
|
await mediaFetchService.clearSizedThumbnailDiskCache();
|
||||||
imageCache.clear();
|
imageCache.clear();
|
||||||
_reload();
|
_reload();
|
||||||
|
|
|
@ -233,13 +233,13 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_licenses.add(const Padding(
|
_licenses.add(const Padding(
|
||||||
padding: EdgeInsets.all(18.0),
|
padding: EdgeInsets.all(18),
|
||||||
child: Divider(),
|
child: Divider(),
|
||||||
));
|
));
|
||||||
for (final LicenseParagraph paragraph in paragraphs) {
|
for (final LicenseParagraph paragraph in paragraphs) {
|
||||||
if (paragraph.indent == LicenseParagraph.centeredIndent) {
|
if (paragraph.indent == LicenseParagraph.centeredIndent) {
|
||||||
_licenses.add(Padding(
|
_licenses.add(Padding(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
padding: const EdgeInsets.only(top: 16),
|
||||||
child: Text(
|
child: Text(
|
||||||
paragraph.text,
|
paragraph.text,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
@ -249,7 +249,7 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
|
||||||
} else {
|
} else {
|
||||||
assert(paragraph.indent >= 0);
|
assert(paragraph.indent >= 0);
|
||||||
_licenses.add(Padding(
|
_licenses.add(Padding(
|
||||||
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
|
padding: EdgeInsetsDirectional.only(top: 8, start: 16.0 * paragraph.indent),
|
||||||
child: Text(paragraph.text),
|
child: Text(paragraph.text),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
|
||||||
..._licenses,
|
..._licenses,
|
||||||
if (!_loaded)
|
if (!_loaded)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 24.0),
|
padding: EdgeInsets.symmetric(vertical: 24),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/filters/recent.dart';
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/settings/defaults.dart';
|
import 'package:aves/model/settings/defaults.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
|
||||||
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
|
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
|
||||||
import 'package:aves/model/settings/enums/screen_on.dart';
|
import 'package:aves/model/settings/enums/screen_on.dart';
|
||||||
import 'package:aves/model/settings/enums/theme_brightness.dart';
|
import 'package:aves/model/settings/enums/theme_brightness.dart';
|
||||||
|
@ -325,7 +324,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(Theme.of(context)));
|
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(Theme.of(context)));
|
||||||
}
|
}
|
||||||
return Selector<Settings, bool>(
|
return Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.initialized ? s.accessibilityAnimations.animate : true,
|
selector: (context, s) => s.initialized ? s.animate : true,
|
||||||
builder: (context, areAnimationsEnabled, child) {
|
builder: (context, areAnimationsEnabled, child) {
|
||||||
return FutureBuilder<bool>(
|
return FutureBuilder<bool>(
|
||||||
future: _shouldUseBoldFontLoader,
|
future: _shouldUseBoldFontLoader,
|
||||||
|
@ -668,7 +667,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
class AvesScrollBehavior extends MaterialScrollBehavior {
|
class AvesScrollBehavior extends MaterialScrollBehavior {
|
||||||
@override
|
@override
|
||||||
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
|
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
|
||||||
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
|
final animate = context.select<Settings, bool>((v) => v.animate);
|
||||||
return animate
|
return animate
|
||||||
? StretchingOverscrollIndicator(
|
? StretchingOverscrollIndicator(
|
||||||
axisDirection: details.direction,
|
axisDirection: details.direction,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
@ -171,7 +172,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
selector: (context, s) => s.collectionBrowsingQuickActions,
|
selector: (context, s) => s.collectionBrowsingQuickActions,
|
||||||
builder: (context, _, child) {
|
builder: (context, _, child) {
|
||||||
final useTvLayout = settings.useTvLayout;
|
final useTvLayout = settings.useTvLayout;
|
||||||
final actions = _buildActions(context, selection);
|
|
||||||
final onFilterTap = canRemoveFilters ? collection.removeFilter : null;
|
final onFilterTap = canRemoveFilters ? collection.removeFilter : null;
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: appBarContentHeight,
|
contentHeight: appBarContentHeight,
|
||||||
|
@ -181,7 +181,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
isSelecting: isSelecting,
|
isSelecting: isSelecting,
|
||||||
),
|
),
|
||||||
title: _buildAppBarTitle(isSelecting),
|
title: _buildAppBarTitle(isSelecting),
|
||||||
actions: useTvLayout ? [] : actions,
|
actions: (context, maxWidth) => useTvLayout ? [] : _buildActions(context, selection, maxWidth),
|
||||||
bottom: Column(
|
bottom: Column(
|
||||||
children: [
|
children: [
|
||||||
if (useTvLayout)
|
if (useTvLayout)
|
||||||
|
@ -190,7 +190,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: actions,
|
children: _buildActions(context, selection, double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showFilterBar)
|
if (showFilterBar)
|
||||||
|
@ -301,7 +301,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context, Selection<AvesEntry> selection) {
|
List<Widget> _buildActions(BuildContext context, Selection<AvesEntry> selection, double maxWidth) {
|
||||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||||
final isSelecting = selection.isSelecting;
|
final isSelecting = selection.isSelecting;
|
||||||
final selectedItemCount = selection.selectedItems.length;
|
final selectedItemCount = selection.selectedItems.length;
|
||||||
|
@ -333,6 +333,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
context: context,
|
context: context,
|
||||||
appMode: appMode,
|
appMode: appMode,
|
||||||
selection: selection,
|
selection: selection,
|
||||||
|
maxWidth: maxWidth,
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
canApply: canApply,
|
canApply: canApply,
|
||||||
);
|
);
|
||||||
|
@ -366,20 +367,29 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double _iconButtonWidth(BuildContext context) {
|
||||||
|
const defaultPadding = EdgeInsets.all(8);
|
||||||
|
const defaultIconSize = 24.0;
|
||||||
|
return defaultPadding.horizontal + MediaQuery.textScalerOf(context).scale(defaultIconSize);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> _buildMobileActions({
|
List<Widget> _buildMobileActions({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required AppMode appMode,
|
required AppMode appMode,
|
||||||
required Selection<AvesEntry> selection,
|
required Selection<AvesEntry> selection,
|
||||||
|
required double maxWidth,
|
||||||
required bool Function(EntrySetAction action) isVisible,
|
required bool Function(EntrySetAction action) isVisible,
|
||||||
required bool Function(EntrySetAction action) canApply,
|
required bool Function(EntrySetAction action) canApply,
|
||||||
}) {
|
}) {
|
||||||
|
final availableCount = (maxWidth / _iconButtonWidth(context)).floor();
|
||||||
|
|
||||||
final isSelecting = selection.isSelecting;
|
final isSelecting = selection.isSelecting;
|
||||||
final selectedItemCount = selection.selectedItems.length;
|
final selectedItemCount = selection.selectedItems.length;
|
||||||
final hasSelection = selectedItemCount > 0;
|
final hasSelection = selectedItemCount > 0;
|
||||||
|
|
||||||
final browsingQuickActions = settings.collectionBrowsingQuickActions;
|
final browsingQuickActions = settings.collectionBrowsingQuickActions;
|
||||||
final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions;
|
final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions;
|
||||||
final quickActions = isSelecting ? selectionQuickActions : browsingQuickActions;
|
final quickActions = (isSelecting ? selectionQuickActions : browsingQuickActions).take(max(0, availableCount - 1)).toList();
|
||||||
final quickActionButtons = quickActions.where(isVisible).map(
|
final quickActionButtons = quickActions.where(isVisible).map(
|
||||||
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
|
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
|
||||||
);
|
);
|
||||||
|
@ -396,7 +406,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
|
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
|
||||||
);
|
);
|
||||||
|
|
||||||
final allContextualActions = isSelecting ? EntrySetActions.pageSelection: EntrySetActions.pageBrowsing;
|
final allContextualActions = isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing;
|
||||||
final contextualMenuActions = allContextualActions.where(_isValidForMenu).fold(<EntrySetAction?>[], (prev, v) {
|
final contextualMenuActions = allContextualActions.where(_isValidForMenu).fold(<EntrySetAction?>[], (prev, v) {
|
||||||
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
||||||
return [...prev, v];
|
return [...prev, v];
|
||||||
|
@ -444,7 +454,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<AvesEntry> _getExpandedSelectedItems(Selection<AvesEntry> selection) {
|
Set<AvesEntry> _getExpandedSelectedItems(Selection<AvesEntry> selection) {
|
||||||
return selection.selectedItems.expand((entry) => entry.burstEntries ?? {entry}).toSet();
|
return selection.selectedItems.expand((entry) => entry.stackedEntries ?? {entry}).toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// key is expected by test driver (e.g. 'menu-configureView', 'menu-map')
|
// key is expected by test driver (e.g. 'menu-configureView', 'menu-map')
|
||||||
|
|
|
@ -7,10 +7,10 @@ import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/services/app_service.dart';
|
||||||
import 'package:aves/services/intent_service.dart';
|
import 'package:aves/services/intent_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/collection/collection_grid.dart';
|
import 'package:aves/widgets/collection/collection_grid.dart';
|
||||||
|
@ -24,6 +24,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_fab.dart';
|
import 'package:aves/widgets/common/identity/aves_fab.dart';
|
||||||
import 'package:aves/widgets/common/providers/query_provider.dart';
|
import 'package:aves/widgets/common/providers/query_provider.dart';
|
||||||
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
|
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
|
||||||
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_rail.dart';
|
import 'package:aves/widgets/navigation/tv_rail.dart';
|
||||||
|
@ -186,10 +187,21 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
return hasSelection
|
return hasSelection
|
||||||
? AvesFab(
|
? AvesFab(
|
||||||
tooltip: context.l10n.pickTooltip,
|
tooltip: context.l10n.pickTooltip,
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
final items = context.read<Selection<AvesEntry>>().selectedItems;
|
final items = context.read<Selection<AvesEntry>>().selectedItems;
|
||||||
final uris = items.map((entry) => entry.uri).toList();
|
final uris = items.map((entry) => entry.uri).toList();
|
||||||
IntentService.submitPickedItems(uris);
|
try {
|
||||||
|
await IntentService.submitPickedItems(uris);
|
||||||
|
} on TooManyItemsException catch (_) {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AvesDialog(
|
||||||
|
content: Text(context.l10n.tooManyItemsErrorDialogMessage),
|
||||||
|
actions: const [OkButton()],
|
||||||
|
),
|
||||||
|
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
@ -217,7 +229,7 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
await Future.delayed(delayDuration + ADurations.highlightScrollInitDelay);
|
await Future.delayed(delayDuration + ADurations.highlightScrollInitDelay);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final animate = context.read<Settings>().accessibilityAnimations.animate;
|
final animate = context.read<Settings>().animate;
|
||||||
context.read<HighlightInfo>().trackItem(item, animate: animate, highlightItem: item);
|
context.read<HighlightInfo>().trackItem(item, animate: animate, highlightItem: item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||||
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/favourites.dart';
|
import 'package:aves/model/favourites.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
@ -20,6 +21,7 @@ import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/app_service.dart';
|
import 'package:aves/services/app_service.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
|
@ -34,6 +36,7 @@ import 'package:aves/widgets/common/search/route.dart';
|
||||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_page.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_page.dart';
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/location_pick_page.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/location_pick_page.dart';
|
||||||
import 'package:aves/widgets/map/map_page.dart';
|
import 'package:aves/widgets/map/map_page.dart';
|
||||||
|
@ -237,7 +240,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
Set<AvesEntry> _getTargetItems(BuildContext context) {
|
Set<AvesEntry> _getTargetItems(BuildContext context) {
|
||||||
final selection = context.read<Selection<AvesEntry>>();
|
final selection = context.read<Selection<AvesEntry>>();
|
||||||
final groupedEntries = (selection.isSelecting ? selection.selectedItems : context.read<CollectionLens>().sortedEntries);
|
final groupedEntries = (selection.isSelecting ? selection.selectedItems : context.read<CollectionLens>().sortedEntries);
|
||||||
return groupedEntries.expand((entry) => entry.burstEntries ?? {entry}).toSet();
|
return groupedEntries.expand((entry) => entry.stackedEntries ?? {entry}).toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _share(BuildContext context) async {
|
Future<void> _share(BuildContext context) async {
|
||||||
|
@ -366,9 +369,23 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
_browse(context);
|
_browse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _convert(BuildContext context) {
|
Future<void> _convert(BuildContext context) async {
|
||||||
final entries = _getTargetItems(context);
|
final entries = _getTargetItems(context);
|
||||||
convert(context, entries);
|
|
||||||
|
final options = await showDialog<EntryConvertOptions>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConvertEntryDialog(entries: entries),
|
||||||
|
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
||||||
|
);
|
||||||
|
if (options == null) return;
|
||||||
|
|
||||||
|
switch (options.action) {
|
||||||
|
case EntryConvertAction.convert:
|
||||||
|
await doExport(context, entries, options);
|
||||||
|
case EntryConvertAction.convertMotionPhotoToStillImage:
|
||||||
|
final todoItems = entries.where((entry) => entry.isMotionPhoto).toSet();
|
||||||
|
await _edit(context, todoItems, (entry) => entry.removeTrailerVideo());
|
||||||
|
}
|
||||||
|
|
||||||
_browse(context);
|
_browse(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ class AlbumSectionHeader extends StatelessWidget {
|
||||||
title: albumName ?? context.l10n.sectionUnknown,
|
title: albumName ?? context.l10n.sectionUnknown,
|
||||||
trailing: _directory != null && androidFileUtils.isOnRemovableStorage(_directory)
|
trailing: _directory != null && androidFileUtils.isOnRemovableStorage(_directory)
|
||||||
? const Icon(
|
? const Icon(
|
||||||
AIcons.removableStorage,
|
AIcons.storageCard,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Color(0xFF757575),
|
color: Color(0xFF757575),
|
||||||
)
|
)
|
||||||
|
|
|
@ -80,7 +80,7 @@ class EntryListDetails extends StatelessWidget {
|
||||||
final date = entry.bestDate;
|
final date = entry.bestDate;
|
||||||
final dateText = date != null ? formatDateTime(date, locale, use24hour) : AText.valueNotAvailable;
|
final dateText = date != null ? formatDateTime(date, locale, use24hour) : AText.valueNotAvailable;
|
||||||
|
|
||||||
final size = entry.burstEntries?.map((v) => v.sizeBytes).sum ?? entry.sizeBytes;
|
final size = entry.stackedEntries?.map((v) => v.sizeBytes).sum ?? entry.sizeBytes;
|
||||||
final sizeText = size != null ? formatFileSize(locale, size) : AText.valueNotAvailable;
|
final sizeText = size != null ? formatFileSize(locale, size) : AText.valueNotAvailable;
|
||||||
|
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
|
|
@ -35,7 +35,7 @@ class AlbumQuickChooser extends StatelessWidget {
|
||||||
pointerGlobalPosition: pointerGlobalPosition,
|
pointerGlobalPosition: pointerGlobalPosition,
|
||||||
itemBuilder: (context, album) => AvesFilterChip(
|
itemBuilder: (context, album) => AvesFilterChip(
|
||||||
filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
|
filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
|
||||||
showGenericIcon: false,
|
allowGenericIcon: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class TagQuickChooser extends StatelessWidget {
|
||||||
pointerGlobalPosition: pointerGlobalPosition,
|
pointerGlobalPosition: pointerGlobalPosition,
|
||||||
itemBuilder: (context, filter) => AvesFilterChip(
|
itemBuilder: (context, filter) => AvesFilterChip(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
showGenericIcon: false,
|
allowGenericIcon: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/model/multipage.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
|
@ -27,7 +28,6 @@ import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
|
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||||
|
@ -37,14 +37,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
Future<void> convert(BuildContext context, Set<AvesEntry> targetEntries) async {
|
Future<void> doExport(BuildContext context, Set<AvesEntry> targetEntries, EntryConvertOptions options) async {
|
||||||
final options = await showDialog<EntryConvertOptions>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ConvertEntryDialog(entries: targetEntries),
|
|
||||||
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
|
||||||
);
|
|
||||||
if (options == null) return;
|
|
||||||
|
|
||||||
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
||||||
if (destinationAlbum == null) return;
|
if (destinationAlbum == null) return;
|
||||||
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
||||||
|
@ -70,6 +63,34 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
var nameConflictStrategy = NameConflictStrategy.rename;
|
||||||
|
final destinationDirectory = Directory(destinationAlbum);
|
||||||
|
final destinationExtension = MimeTypes.extensionFor(options.mimeType);
|
||||||
|
final names = [
|
||||||
|
...selection.map((v) => '${v.filenameWithoutExtension}$destinationExtension'),
|
||||||
|
// do not guard up front based on directory existence,
|
||||||
|
// as conflicts could be within moved entries scattered across multiple albums
|
||||||
|
if (await destinationDirectory.exists()) ...destinationDirectory.listSync().map((v) => pContext.basename(v.path)),
|
||||||
|
].map((v) => v.toLowerCase()).toList();
|
||||||
|
// case insensitive comparison
|
||||||
|
final uniqueNames = names.toSet();
|
||||||
|
if (uniqueNames.length < names.length) {
|
||||||
|
final value = await showDialog<NameConflictStrategy>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AvesSingleSelectionDialog<NameConflictStrategy>(
|
||||||
|
initialValue: nameConflictStrategy,
|
||||||
|
options: Map.fromEntries(NameConflictStrategy.values.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
|
message: l10n.nameConflictDialogSingleSourceMessage,
|
||||||
|
confirmationButtonLabel: l10n.continueButtonLabel,
|
||||||
|
),
|
||||||
|
routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName),
|
||||||
|
);
|
||||||
|
if (value == null) return;
|
||||||
|
nameConflictStrategy = value;
|
||||||
|
}
|
||||||
|
|
||||||
final selectionCount = selection.length;
|
final selectionCount = selection.length;
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
source.pauseMonitoring();
|
source.pauseMonitoring();
|
||||||
|
@ -79,7 +100,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
selection,
|
selection,
|
||||||
options: options,
|
options: options,
|
||||||
destinationAlbum: destinationAlbum,
|
destinationAlbum: destinationAlbum,
|
||||||
nameConflictStrategy: NameConflictStrategy.rename,
|
nameConflictStrategy: nameConflictStrategy,
|
||||||
),
|
),
|
||||||
itemCount: selectionCount,
|
itemCount: selectionCount,
|
||||||
onDone: (processed) async {
|
onDone: (processed) async {
|
||||||
|
@ -91,7 +112,6 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
source.resumeMonitoring();
|
source.resumeMonitoring();
|
||||||
unawaited(source.refreshUris(newUris));
|
unawaited(source.refreshUris(newUris));
|
||||||
|
|
||||||
final l10n = context.l10n;
|
|
||||||
// get navigator beforehand because
|
// get navigator beforehand because
|
||||||
// local context may be deactivated when action is triggered after navigation
|
// local context may be deactivated when action is triggered after navigation
|
||||||
final navigator = Navigator.maybeOf(context);
|
final navigator = Navigator.maybeOf(context);
|
||||||
|
@ -173,7 +193,8 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
// do not guard up front based on directory existence,
|
// do not guard up front based on directory existence,
|
||||||
// as conflicts could be within moved entries scattered across multiple albums
|
// as conflicts could be within moved entries scattered across multiple albums
|
||||||
if (await destinationDirectory.exists()) ...destinationDirectory.listSync().map((v) => pContext.basename(v.path)),
|
if (await destinationDirectory.exists()) ...destinationDirectory.listSync().map((v) => pContext.basename(v.path)),
|
||||||
];
|
].map((v) => v.toLowerCase()).toList();
|
||||||
|
// case insensitive comparison
|
||||||
final uniqueNames = names.toSet();
|
final uniqueNames = names.toSet();
|
||||||
if (uniqueNames.length < names.length) {
|
if (uniqueNames.length < names.length) {
|
||||||
final value = await showDialog<NameConflictStrategy>(
|
final value = await showDialog<NameConflictStrategy>(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
|
||||||
import 'package:aves/model/settings/enums/accessibility_timeout.dart';
|
import 'package:aves/model/settings/enums/accessibility_timeout.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
|
@ -122,7 +121,7 @@ mixin FeedbackMixin {
|
||||||
|
|
||||||
static double snackBarHorizontalPadding(SnackBarThemeData snackBarTheme) {
|
static double snackBarHorizontalPadding(SnackBarThemeData snackBarTheme) {
|
||||||
final isFloatingSnackBar = (snackBarTheme.behavior ?? SnackBarBehavior.fixed) == SnackBarBehavior.floating;
|
final isFloatingSnackBar = (snackBarTheme.behavior ?? SnackBarBehavior.fixed) == SnackBarBehavior.floating;
|
||||||
final horizontalPadding = isFloatingSnackBar ? 16.0 : 24.0;
|
final double horizontalPadding = isFloatingSnackBar ? 16.0 : 24.0;
|
||||||
return horizontalPadding;
|
return horizontalPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +181,9 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
|
||||||
|
|
||||||
Stream<T> get opStream => widget.opStream;
|
Stream<T> get opStream => widget.opStream;
|
||||||
|
|
||||||
static const fontSize = 18.0;
|
static const double fontSize = 18.0;
|
||||||
static const diameter = 160.0;
|
static const double diameter = 160.0;
|
||||||
static const strokeWidth = 8.0;
|
static const double strokeWidth = 8.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -224,7 +223,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
final progressColor = colorScheme.primary;
|
final progressColor = colorScheme.primary;
|
||||||
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
|
final animate = context.select<Settings, bool>((v) => v.animate);
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
child: StreamBuilder<T>(
|
child: StreamBuilder<T>(
|
||||||
|
|
|
@ -212,7 +212,7 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
final IconButton? iconButton = showCloseIcon
|
final IconButton? iconButton = showCloseIcon
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
iconSize: 24.0,
|
iconSize: 24,
|
||||||
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,
|
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,
|
||||||
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
|
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
|
||||||
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
||||||
|
|
|
@ -10,7 +10,7 @@ class ArrowClipper extends CustomClipper<Path> {
|
||||||
path.lineTo(0.0, 0.0);
|
path.lineTo(0.0, 0.0);
|
||||||
path.close();
|
path.close();
|
||||||
|
|
||||||
const arrowWidth = 8.0;
|
const double arrowWidth = 8.0;
|
||||||
final startPointX = (size.width - arrowWidth) / 2;
|
final startPointX = (size.width - arrowWidth) / 2;
|
||||||
var startPointY = size.height / 2 - arrowWidth / 2;
|
var startPointY = size.height / 2 - arrowWidth / 2;
|
||||||
path.moveTo(startPointX, startPointY);
|
path.moveTo(startPointX, startPointY);
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LinkChip extends StatelessWidget {
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
onTap: onTap ?? () => AvesApp.launchUrl(urlString),
|
onTap: onTap ?? () => AvesApp.launchUrl(urlString),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -11,7 +11,7 @@ class AvesPopupMenuButton<T> extends PopupMenuButton<T> {
|
||||||
super.onCanceled,
|
super.onCanceled,
|
||||||
super.tooltip,
|
super.tooltip,
|
||||||
super.elevation,
|
super.elevation,
|
||||||
super.padding = const EdgeInsets.all(8.0),
|
super.padding = const EdgeInsets.all(8),
|
||||||
super.child,
|
super.child,
|
||||||
super.icon,
|
super.icon,
|
||||||
super.offset = Offset.zero,
|
super.offset = Offset.zero,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/explorer/explorer_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
@ -32,7 +33,7 @@ class TvNavigationPopHandler {
|
||||||
|
|
||||||
return switch (homePage) {
|
return switch (homePage) {
|
||||||
HomePageSetting.collection => context.read<CollectionLens>().filters.isEmpty,
|
HomePageSetting.collection => context.read<CollectionLens>().filters.isEmpty,
|
||||||
HomePageSetting.albums || HomePageSetting.tags => true,
|
HomePageSetting.albums || HomePageSetting.tags || HomePageSetting.explorer => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ class TvNavigationPopHandler {
|
||||||
HomePageSetting.collection => buildRoute((context) => CollectionPage(source: context.read<CollectionSource>(), filters: null)),
|
HomePageSetting.collection => buildRoute((context) => CollectionPage(source: context.read<CollectionSource>(), filters: null)),
|
||||||
HomePageSetting.albums => buildRoute((context) => const AlbumListPage()),
|
HomePageSetting.albums => buildRoute((context) => const AlbumListPage()),
|
||||||
HomePageSetting.tags => buildRoute((context) => const TagListPage()),
|
HomePageSetting.tags => buildRoute((context) => const TagListPage()),
|
||||||
|
HomePageSetting.explorer => buildRoute((context) => const ExplorerPage()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ class ExpandableFilterRow extends StatelessWidget {
|
||||||
// key `album-{path}` is expected by test driver
|
// key `album-{path}` is expected by test driver
|
||||||
key: Key(filter.key),
|
key: Key(filter.key),
|
||||||
filter: filter,
|
filter: filter,
|
||||||
showGenericIcon: showGenericIcon,
|
allowGenericIcon: showGenericIcon,
|
||||||
leadingOverride: leadingBuilder?.call(filter),
|
leadingOverride: leadingBuilder?.call(filter),
|
||||||
heroType: heroTypeBuilder?.call(filter) ?? HeroType.onTap,
|
heroType: heroTypeBuilder?.call(filter) ?? HeroType.onTap,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue