mirror of
https://github.com/nextcloud/android.git
synced 2024-12-20 07:52:18 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
05d37164ff
44 changed files with 882 additions and 506 deletions
22
.github/workflows/analysis.yml
vendored
22
.github/workflows/analysis.yml
vendored
|
@ -24,14 +24,18 @@ jobs:
|
|||
run: |
|
||||
if [ -z "$GITHUB_HEAD_REF" ]; then
|
||||
# push
|
||||
echo "branch=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "pr=$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
|
||||
echo "repo=${{ github.repository }}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "branch=$GITHUB_REF_NAME"
|
||||
echo "pr=$GITHUB_RUN_ID"
|
||||
echo "repo=${{ github.repository }}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# pull request
|
||||
echo "branch=$GITHUB_HEAD_REF" >> "$GITHUB_OUTPUT"
|
||||
echo "pr=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
|
||||
echo "repo=${{ github.event.pull_request.head.repo.full_name }}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "branch=$GITHUB_HEAD_REF"
|
||||
echo "pr=${{ github.event.pull_request.number }}"
|
||||
echo "repo=${{ github.event.pull_request.head.repo.full_name }}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
|
@ -49,6 +53,6 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > $HOME/.gradle/gradle.properties
|
||||
scripts/analysis/analysis-wrapper.sh ${{ steps.get-vars.outputs.branch }} ${{ secrets.LOG_USERNAME }} ${{ secrets.LOG_PASSWORD }} $GITHUB_RUN_NUMBER ${{ steps.get-vars.outputs.pr }}
|
||||
mkdir -p "$HOME/.gradle"
|
||||
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
|
||||
scripts/analysis/analysis-wrapper.sh ${{ steps.get-vars.outputs.branch }} ${{ secrets.LOG_USERNAME }} ${{ secrets.LOG_PASSWORD }} "$GITHUB_RUN_NUMBER" ${{ steps.get-vars.outputs.pr }}
|
||||
|
|
1
.github/workflows/stale.yml
vendored
1
.github/workflows/stale.yml
vendored
|
@ -26,3 +26,4 @@ jobs:
|
|||
Please take a look again and update the issue with new details,
|
||||
otherwise the issue will be automatically closed in 2 weeks. Thank you!
|
||||
exempt-all-pr-milestones: true
|
||||
labels-to-remove-when-unstale: 'needs info'
|
||||
|
|
|
@ -104,7 +104,7 @@ class BackgroundJobManagerTest {
|
|||
clock = mock()
|
||||
whenever(clock.currentTime).thenReturn(TIMESTAMP)
|
||||
whenever(clock.currentDate).thenReturn(Date(TIMESTAMP))
|
||||
backgroundJobManager = BackgroundJobManagerImpl(workManager, clock)
|
||||
backgroundJobManager = BackgroundJobManagerImpl(workManager, clock, mock())
|
||||
}
|
||||
|
||||
fun assertHasRequiredTags(tags: Set<String>, jobName: String, user: User? = null) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.Manifest
|
|||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.work.WorkManager
|
||||
import com.nextcloud.client.core.ClockImpl
|
||||
import com.nextcloud.client.preferences.AppPreferencesImpl
|
||||
import com.nextcloud.test.RetryTestRule
|
||||
import com.owncloud.android.AbstractIT
|
||||
import com.owncloud.android.AbstractOnServerIT
|
||||
|
@ -43,7 +44,8 @@ import java.io.FileInputStream
|
|||
|
||||
class ContactsBackupIT : AbstractOnServerIT() {
|
||||
val workmanager = WorkManager.getInstance(targetContext)
|
||||
private val backgroundJobManager = BackgroundJobManagerImpl(workmanager, ClockImpl())
|
||||
val preferences = AppPreferencesImpl.fromContext(targetContext)
|
||||
private val backgroundJobManager = BackgroundJobManagerImpl(workmanager, ClockImpl(), preferences)
|
||||
|
||||
@get:Rule
|
||||
val writeContactsRule = GrantPermissionRule.grant(Manifest.permission.WRITE_CONTACTS)
|
||||
|
|
|
@ -139,12 +139,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
|
|||
val file = getDummyFile("chunkedFile.txt")
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
targetContext,
|
||||
user,
|
||||
file.absolutePath,
|
||||
"/testFile.txt",
|
||||
FileUploader.LOCAL_BEHAVIOUR_COPY,
|
||||
null,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
@ -258,12 +256,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
|
|||
val file = getDummyFile("nonEmpty.txt")
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
targetContext,
|
||||
user,
|
||||
file.absolutePath,
|
||||
"/testFile.txt",
|
||||
FileUploader.LOCAL_BEHAVIOUR_COPY,
|
||||
null,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
@ -369,12 +365,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
|
|||
val file = getDummyFile("chunkedFile.txt")
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
targetContext,
|
||||
user,
|
||||
file.absolutePath,
|
||||
"/testFile.txt",
|
||||
FileUploader.LOCAL_BEHAVIOUR_COPY,
|
||||
null,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
@ -476,12 +470,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
|
|||
val file = getDummyFile("chunkedFile.txt")
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
targetContext,
|
||||
user,
|
||||
file.absolutePath,
|
||||
"/testFile.txt",
|
||||
FileUploader.LOCAL_BEHAVIOUR_COPY,
|
||||
null,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
@ -23,8 +23,11 @@ package com.nextcloud.client.di;
|
|||
import com.nextcloud.client.documentscan.DocumentScanActivity;
|
||||
import com.nextcloud.client.editimage.EditImageActivity;
|
||||
import com.nextcloud.client.etm.EtmActivity;
|
||||
import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment;
|
||||
import com.nextcloud.client.files.downloader.FileTransferService;
|
||||
import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
|
||||
import com.nextcloud.client.jobs.NotificationWork;
|
||||
import com.nextcloud.client.jobs.TestJob;
|
||||
import com.nextcloud.client.logger.ui.LogsActivity;
|
||||
import com.nextcloud.client.logger.ui.LogsViewModel;
|
||||
import com.nextcloud.client.media.PlayerService;
|
||||
|
@ -478,4 +481,13 @@ abstract class ComponentsModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
abstract ImageDetailFragment imageDetailFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract EtmBackgroundJobsFragment etmBackgroundJobsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract BackgroundJobManagerImpl backgroundJobManagerImpl();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract TestJob testJob();
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import com.owncloud.android.files.services.FileUploader
|
|||
import com.owncloud.android.files.services.NameCollisionPolicy
|
||||
import com.owncloud.android.operations.UploadFileOperation
|
||||
import com.owncloud.android.ui.helpers.FileOperationsHelper
|
||||
import com.owncloud.android.utils.MimeType
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -184,16 +183,10 @@ class DocumentScanViewModel @Inject constructor(
|
|||
uploadFolder + OCFile.PATH_SEPARATOR + File(it).name
|
||||
}.toTypedArray()
|
||||
|
||||
val mimetypes = pageList.map {
|
||||
MimeType.JPEG
|
||||
}.toTypedArray()
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
getApplication(),
|
||||
currentAccountProvider.user,
|
||||
pageList.toTypedArray(),
|
||||
uploadPaths,
|
||||
mimetypes,
|
||||
FileUploader.LOCAL_BEHAVIOUR_DELETE,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
|
|
|
@ -39,7 +39,6 @@ import com.owncloud.android.files.services.FileUploader
|
|||
import com.owncloud.android.files.services.NameCollisionPolicy
|
||||
import com.owncloud.android.operations.UploadFileOperation
|
||||
import com.owncloud.android.ui.notifications.NotificationUtils
|
||||
import com.owncloud.android.utils.MimeType
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
|
@ -124,12 +123,10 @@ class GeneratePdfFromImagesWork(
|
|||
val uploadPath = uploadFolder + OCFile.PATH_SEPARATOR + File(pdfPath).name
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
appContext,
|
||||
user,
|
||||
pdfPath,
|
||||
uploadPath,
|
||||
FileUploader.LOCAL_BEHAVIOUR_DELETE, // MIME type will be detected from file name
|
||||
MimeType.PDF,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
package com.nextcloud.client.etm.pages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
|
@ -32,15 +33,23 @@ import androidx.lifecycle.Observer
|
|||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nextcloud.client.di.Injectable
|
||||
import com.nextcloud.client.etm.EtmBaseFragment
|
||||
import com.nextcloud.client.jobs.BackgroundJobManagerImpl
|
||||
import com.nextcloud.client.jobs.JobInfo
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.owncloud.android.R
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
||||
class EtmBackgroundJobsFragment : EtmBaseFragment(), Injectable {
|
||||
|
||||
class Adapter(private val inflater: LayoutInflater) : RecyclerView.Adapter<Adapter.ViewHolder>() {
|
||||
@Inject
|
||||
lateinit var preferences: AppPreferences
|
||||
|
||||
class Adapter(private val inflater: LayoutInflater, private val preferences: AppPreferences) :
|
||||
RecyclerView.Adapter<Adapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val uuid = view.findViewById<TextView>(R.id.etm_background_job_uuid)
|
||||
|
@ -50,6 +59,10 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
val started = view.findViewById<TextView>(R.id.etm_background_job_started)
|
||||
val progress = view.findViewById<TextView>(R.id.etm_background_job_progress)
|
||||
private val progressRow = view.findViewById<View>(R.id.etm_background_job_progress_row)
|
||||
val executionCount = view.findViewById<TextView>(R.id.etm_background_execution_count)
|
||||
val executionLog = view.findViewById<TextView>(R.id.etm_background_execution_logs)
|
||||
private val executionLogRow = view.findViewById<View>(R.id.etm_background_execution_logs_row)
|
||||
val executionTimesRow = view.findViewById<View>(R.id.etm_background_execution_times_row)
|
||||
|
||||
var progressEnabled: Boolean = progressRow.visibility == View.VISIBLE
|
||||
get() {
|
||||
|
@ -63,6 +76,19 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
var logsEnabled: Boolean = executionLogRow.visibility == View.VISIBLE
|
||||
get() {
|
||||
return executionLogRow.visibility == View.VISIBLE
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
executionLogRow.visibility = if (value) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:MM:ssZ", Locale.getDefault())
|
||||
|
@ -74,13 +100,20 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = inflater.inflate(R.layout.etm_background_job_list_item, parent, false)
|
||||
return ViewHolder(view)
|
||||
val viewHolder = ViewHolder(view)
|
||||
viewHolder.logsEnabled = false
|
||||
viewHolder.executionTimesRow.visibility = View.GONE
|
||||
view.setOnClickListener {
|
||||
viewHolder.logsEnabled = !viewHolder.logsEnabled
|
||||
}
|
||||
return viewHolder
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return backgroundJobs.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(vh: ViewHolder, position: Int) {
|
||||
val info = backgroundJobs[position]
|
||||
vh.uuid.text = info.id.toString()
|
||||
|
@ -94,6 +127,34 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
} else {
|
||||
vh.progressEnabled = false
|
||||
}
|
||||
|
||||
val logs = preferences.readLogEntry()
|
||||
val logsForThisWorker =
|
||||
logs.filter { BackgroundJobManagerImpl.parseTag(it.workerClass)?.second == info.workerClass }
|
||||
if (logsForThisWorker.isNotEmpty()) {
|
||||
vh.executionTimesRow.visibility = View.VISIBLE
|
||||
vh.executionCount.text =
|
||||
"${logsForThisWorker.filter { it.started != null }.size} " +
|
||||
"(${logsForThisWorker.filter { it.finished != null }.size})"
|
||||
var logText = "Worker Logs\n\n" +
|
||||
"*** Does NOT differentiate between immediate or periodic kinds of Work! ***\n" +
|
||||
"*** Times run in 48h: Times started (Times finished) ***\n"
|
||||
logsForThisWorker.forEach {
|
||||
logText += "----------------------\n"
|
||||
logText += "Worker ${BackgroundJobManagerImpl.parseTag(it.workerClass)?.second}\n"
|
||||
logText += if (it.started == null) {
|
||||
"ENDED at\n${it.finished}\nWith result: ${it.result}\n"
|
||||
} else {
|
||||
"STARTED at\n${it.started}\n"
|
||||
}
|
||||
}
|
||||
vh.executionLog.text = logText
|
||||
} else {
|
||||
vh.executionLog.text = "Worker Logs\n\n" +
|
||||
"No Entries -> Maybe logging is not implemented for Worker or it has not run yet."
|
||||
vh.executionCount.text = "0"
|
||||
vh.executionTimesRow.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +168,7 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_etm_background_jobs, container, false)
|
||||
adapter = Adapter(inflater)
|
||||
adapter = Adapter(inflater, preferences)
|
||||
list = view.findViewById(R.id.etm_background_jobs_list)
|
||||
list.layoutManager = LinearLayoutManager(context)
|
||||
list.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||
|
@ -127,22 +188,27 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
|
|||
vm.cancelAllJobs()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.etm_background_jobs_prune -> {
|
||||
vm.pruneJobs()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.etm_background_jobs_start_test -> {
|
||||
vm.startTestJob(periodic = false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.etm_background_jobs_schedule_test -> {
|
||||
vm.startTestJob(periodic = true)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.etm_background_jobs_cancel_test -> {
|
||||
vm.cancelTestJob()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ import javax.inject.Provider
|
|||
*
|
||||
* This class is doing too many things and should be split up into smaller factories.
|
||||
*/
|
||||
@Suppress("LongParameterList") // satisfied by DI
|
||||
@Suppress("LongParameterList", "TooManyFunctions") // satisfied by DI
|
||||
class BackgroundJobFactory @Inject constructor(
|
||||
private val logger: Logger,
|
||||
private val preferences: AppPreferences,
|
||||
|
@ -104,6 +104,7 @@ class BackgroundJobFactory @Inject constructor(
|
|||
FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
|
||||
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
|
||||
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
|
||||
TestJob::class -> createTestJob(context, workerParameters)
|
||||
else -> null // caller falls back to default factory
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +184,8 @@ class BackgroundJobFactory @Inject constructor(
|
|||
uploadsStorageManager = uploadsStorageManager,
|
||||
connectivityService = connectivityService,
|
||||
powerManagementService = powerManagementService,
|
||||
syncedFolderProvider = syncedFolderProvider
|
||||
syncedFolderProvider = syncedFolderProvider,
|
||||
backgroundJobManager = backgroundJobManager.get()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -245,6 +247,7 @@ class BackgroundJobFactory @Inject constructor(
|
|||
accountManager,
|
||||
viewThemeUtils.get(),
|
||||
localBroadcastManager.get(),
|
||||
backgroundJobManager.get(),
|
||||
context,
|
||||
params
|
||||
)
|
||||
|
@ -267,7 +270,16 @@ class BackgroundJobFactory @Inject constructor(
|
|||
context,
|
||||
params,
|
||||
accountManager,
|
||||
arbitraryDataProvider
|
||||
arbitraryDataProvider,
|
||||
backgroundJobManager.get()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTestJob(context: Context, params: WorkerParameters): TestJob {
|
||||
return TestJob(
|
||||
context,
|
||||
params,
|
||||
backgroundJobManager.get()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package com.nextcloud.client.jobs
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.work.ListenableWorker
|
||||
import com.nextcloud.client.account.User
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
|
||||
|
@ -35,6 +36,10 @@ interface BackgroundJobManager {
|
|||
*/
|
||||
val jobs: LiveData<List<JobInfo>>
|
||||
|
||||
fun logStartOfWorker(workerName: String?)
|
||||
|
||||
fun logEndOfWorker(workerName: String?, result: ListenableWorker.Result)
|
||||
|
||||
/**
|
||||
* Start content observer job that monitors changes in media folders
|
||||
* and launches synchronization when needed.
|
||||
|
|
|
@ -36,7 +36,9 @@ import androidx.work.WorkManager
|
|||
import androidx.work.workDataOf
|
||||
import com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.core.Clock
|
||||
import com.nextcloud.client.di.Injectable
|
||||
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
|
@ -60,10 +62,12 @@ import kotlin.reflect.KClass
|
|||
@Suppress("TooManyFunctions") // we expect this implementation to have rich API
|
||||
internal class BackgroundJobManagerImpl(
|
||||
private val workManager: WorkManager,
|
||||
private val clock: Clock
|
||||
) : BackgroundJobManager {
|
||||
private val clock: Clock,
|
||||
private val preferences: AppPreferences
|
||||
) : BackgroundJobManager, Injectable {
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG_ALL = "*" // This tag allows us to retrieve list of all jobs run by Nextcloud client
|
||||
const val JOB_CONTENT_OBSERVER = "content_observer"
|
||||
const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
|
||||
|
@ -82,6 +86,7 @@ internal class BackgroundJobManagerImpl(
|
|||
const val JOB_PDF_GENERATION = "pdf_generation"
|
||||
const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
|
||||
const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
|
||||
|
||||
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
|
||||
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
|
||||
|
||||
|
@ -91,13 +96,16 @@ internal class BackgroundJobManagerImpl(
|
|||
|
||||
const val TAG_PREFIX_NAME = "name"
|
||||
const val TAG_PREFIX_USER = "user"
|
||||
const val TAG_PREFIX_CLASS = "class"
|
||||
const val TAG_PREFIX_START_TIMESTAMP = "timestamp"
|
||||
val PREFIXES = setOf(TAG_PREFIX_NAME, TAG_PREFIX_USER, TAG_PREFIX_START_TIMESTAMP)
|
||||
val PREFIXES = setOf(TAG_PREFIX_NAME, TAG_PREFIX_USER, TAG_PREFIX_START_TIMESTAMP, TAG_PREFIX_CLASS)
|
||||
const val NOT_SET_VALUE = "not set"
|
||||
const val PERIODIC_BACKUP_INTERVAL_MINUTES = 24 * 60L
|
||||
const val DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES = 15L
|
||||
const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
|
||||
|
||||
private const val KEEP_LOG_MILLIS = 1000 * 60 * 60 * 24 * 3L
|
||||
|
||||
fun formatNameTag(name: String, user: User? = null): String {
|
||||
return if (user == null) {
|
||||
"$TAG_PREFIX_NAME:$name"
|
||||
|
@ -107,6 +115,7 @@ internal class BackgroundJobManagerImpl(
|
|||
}
|
||||
|
||||
fun formatUserTag(user: User): String = "$TAG_PREFIX_USER:${user.accountName}"
|
||||
fun formatClassTag(jobClass: KClass<out ListenableWorker>): String = "$TAG_PREFIX_CLASS:${jobClass.simpleName}"
|
||||
fun formatTimeTag(startTimestamp: Long): String = "$TAG_PREFIX_START_TIMESTAMP:$startTimestamp"
|
||||
|
||||
fun parseTag(tag: String): Pair<String, String>? {
|
||||
|
@ -120,11 +129,11 @@ internal class BackgroundJobManagerImpl(
|
|||
}
|
||||
|
||||
fun parseTimestamp(timestamp: String): Date {
|
||||
try {
|
||||
return try {
|
||||
val ms = timestamp.toLong()
|
||||
return Date(ms)
|
||||
Date(ms)
|
||||
} catch (ex: NumberFormatException) {
|
||||
return Date(0)
|
||||
Date(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,12 +152,48 @@ internal class BackgroundJobManagerImpl(
|
|||
name = metadata.get(TAG_PREFIX_NAME) ?: NOT_SET_VALUE,
|
||||
user = metadata.get(TAG_PREFIX_USER) ?: NOT_SET_VALUE,
|
||||
started = timestamp,
|
||||
progress = info.progress.getInt("progress", -1)
|
||||
progress = info.progress.getInt("progress", -1),
|
||||
workerClass = metadata.get(TAG_PREFIX_CLASS) ?: NOT_SET_VALUE
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteOldLogs(logEntries: MutableList<LogEntry>): MutableList<LogEntry> {
|
||||
logEntries.removeIf {
|
||||
return@removeIf (
|
||||
it.started != null &&
|
||||
Date(Date().time - KEEP_LOG_MILLIS).after(it.started)
|
||||
) ||
|
||||
(
|
||||
it.finished != null &&
|
||||
Date(Date().time - KEEP_LOG_MILLIS).after(it.finished)
|
||||
)
|
||||
}
|
||||
return logEntries
|
||||
}
|
||||
}
|
||||
|
||||
override fun logStartOfWorker(workerName: String?) {
|
||||
val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
|
||||
|
||||
if (workerName == null) {
|
||||
logs.add(LogEntry(Date(), null, null, NOT_SET_VALUE))
|
||||
} else {
|
||||
logs.add(LogEntry(Date(), null, null, workerName))
|
||||
}
|
||||
preferences.saveLogEntry(logs)
|
||||
}
|
||||
|
||||
override fun logEndOfWorker(workerName: String?, result: ListenableWorker.Result) {
|
||||
val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
|
||||
if (workerName == null) {
|
||||
logs.add(LogEntry(null, Date(), result.toString(), NOT_SET_VALUE))
|
||||
} else {
|
||||
logs.add(LogEntry(null, Date(), result.toString(), workerName))
|
||||
}
|
||||
preferences.saveLogEntry(logs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,6 +208,7 @@ internal class BackgroundJobManagerImpl(
|
|||
.addTag(TAG_ALL)
|
||||
.addTag(formatNameTag(jobName, user))
|
||||
.addTag(formatTimeTag(clock.currentTime))
|
||||
.addTag(formatClassTag(jobClass))
|
||||
user?.let { builder.addTag(formatUserTag(it)) }
|
||||
return builder
|
||||
}
|
||||
|
@ -187,6 +233,7 @@ internal class BackgroundJobManagerImpl(
|
|||
.addTag(TAG_ALL)
|
||||
.addTag(formatNameTag(jobName, user))
|
||||
.addTag(formatTimeTag(clock.currentTime))
|
||||
.addTag(formatClassTag(jobClass))
|
||||
user?.let { builder.addTag(formatUserTag(it)) }
|
||||
return builder
|
||||
}
|
||||
|
|
|
@ -41,12 +41,17 @@ class ContentObserverWork(
|
|||
) : Worker(appContext, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||
|
||||
if (params.triggeredContentUris.size > 0) {
|
||||
checkAndStartFileSyncJob()
|
||||
backgroundJobManager.startMediaFoldersDetectionJob()
|
||||
}
|
||||
recheduleSelf()
|
||||
return Result.success()
|
||||
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun recheduleSelf() {
|
||||
|
@ -59,4 +64,8 @@ class ContentObserverWork(
|
|||
backgroundJobManager.startImmediateFilesSyncJob(true, false)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = ContentObserverWork::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ class FilesSyncWork(
|
|||
private val uploadsStorageManager: UploadsStorageManager,
|
||||
private val connectivityService: ConnectivityService,
|
||||
private val powerManagementService: PowerManagementService,
|
||||
private val syncedFolderProvider: SyncedFolderProvider
|
||||
private val syncedFolderProvider: SyncedFolderProvider,
|
||||
private val backgroundJobManager: BackgroundJobManager
|
||||
) : Worker(context, params) {
|
||||
|
||||
companion object {
|
||||
|
@ -74,10 +75,14 @@ class FilesSyncWork(
|
|||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||
|
||||
val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
|
||||
// If we are in power save mode, better to postpone upload
|
||||
if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) {
|
||||
return Result.success()
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result
|
||||
}
|
||||
val resources = context.resources
|
||||
val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
|
||||
|
@ -107,7 +112,9 @@ class FilesSyncWork(
|
|||
)
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("LongMethod") // legacy code
|
||||
|
@ -155,7 +162,6 @@ class FilesSyncWork(
|
|||
}
|
||||
val localPaths = pathsAndMimes.map { it.first }.toTypedArray()
|
||||
val remotePaths = pathsAndMimes.map { it.second }.toTypedArray()
|
||||
val mimetypes = pathsAndMimes.map { it.third }.toTypedArray()
|
||||
|
||||
if (lightVersion) {
|
||||
needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging)
|
||||
|
@ -170,12 +176,11 @@ class FilesSyncWork(
|
|||
needsWifi = syncedFolder.isWifiOnly
|
||||
uploadAction = syncedFolder.uploadAction
|
||||
}
|
||||
|
||||
FileUploader.uploadNewFile(
|
||||
context,
|
||||
user,
|
||||
localPaths,
|
||||
remotePaths,
|
||||
mimetypes,
|
||||
uploadAction!!,
|
||||
true, // create parent folder if not existent
|
||||
UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
|
||||
|
|
|
@ -69,6 +69,7 @@ class FilesUploadWorker(
|
|||
val userAccountManager: UserAccountManager,
|
||||
val viewThemeUtils: ViewThemeUtils,
|
||||
val localBroadcastManager: LocalBroadcastManager,
|
||||
private val backgroundJobManager: BackgroundJobManager,
|
||||
val context: Context,
|
||||
params: WorkerParameters
|
||||
) : Worker(context, params), OnDatatransferProgressListener {
|
||||
|
@ -80,10 +81,15 @@ class FilesUploadWorker(
|
|||
private val fileUploaderDelegate = FileUploaderDelegate()
|
||||
|
||||
override fun doWork(): Result {
|
||||
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||
|
||||
val accountName = inputData.getString(ACCOUNT)
|
||||
if (accountName.isNullOrEmpty()) {
|
||||
Log_OC.w(TAG, "User was null for file upload worker")
|
||||
return Result.failure() // user account is needed
|
||||
|
||||
val result = Result.failure()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result // user account is needed
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -100,7 +106,9 @@ class FilesUploadWorker(
|
|||
}
|
||||
|
||||
Log_OC.d(TAG, "No more pending uploads for account $accountName, stopping work")
|
||||
return Result.success()
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result // user account is needed
|
||||
}
|
||||
|
||||
private fun handlePendingUploads(uploads: List<OCUpload>, accountName: String) {
|
||||
|
|
|
@ -42,9 +42,12 @@ class HealthStatusWork(
|
|||
private val context: Context,
|
||||
params: WorkerParameters,
|
||||
private val userAccountManager: UserAccountManager,
|
||||
private val arbitraryDataProvider: ArbitraryDataProvider
|
||||
private val arbitraryDataProvider: ArbitraryDataProvider,
|
||||
private val backgroundJobManager: BackgroundJobManager
|
||||
) : Worker(context, params) {
|
||||
override fun doWork(): Result {
|
||||
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||
|
||||
for (user in userAccountManager.allUsers) {
|
||||
// only if security guard is enabled
|
||||
if (!CapabilityUtils.getCapability(user, context).securityGuard.isTrue) {
|
||||
|
@ -92,7 +95,9 @@ class HealthStatusWork(
|
|||
}
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun collectSyncConflicts(user: User): Problem? {
|
||||
|
|
|
@ -27,6 +27,14 @@ data class JobInfo(
|
|||
val state: String = "",
|
||||
val name: String = "",
|
||||
val user: String = "",
|
||||
val workerClass: String = "",
|
||||
val started: Date = Date(0),
|
||||
val progress: Int = 0
|
||||
)
|
||||
|
||||
data class LogEntry(
|
||||
val started: Date? = null,
|
||||
val finished: Date? = null,
|
||||
val result: String? = null,
|
||||
var workerClass: String = BackgroundJobManagerImpl.NOT_SET_VALUE
|
||||
)
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.content.ContextWrapper
|
|||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.nextcloud.client.core.Clock
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
@ -50,7 +51,11 @@ class JobsModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun backgroundJobManager(workManager: WorkManager, clock: Clock): BackgroundJobManager {
|
||||
return BackgroundJobManagerImpl(workManager, clock)
|
||||
fun backgroundJobManager(
|
||||
workManager: WorkManager,
|
||||
clock: Clock,
|
||||
preferences: AppPreferences
|
||||
): BackgroundJobManager {
|
||||
return BackgroundJobManagerImpl(workManager, clock, preferences)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ import androidx.work.WorkerParameters
|
|||
|
||||
class TestJob(
|
||||
appContext: Context,
|
||||
params: WorkerParameters
|
||||
params: WorkerParameters,
|
||||
private val backgroundJobManager: BackgroundJobManager
|
||||
) : Worker(appContext, params) {
|
||||
|
||||
companion object {
|
||||
|
@ -36,6 +37,8 @@ class TestJob(
|
|||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
|
||||
|
||||
for (i in 0..MAX_PROGRESS) {
|
||||
Thread.sleep(DELAY_MS)
|
||||
val progress = Data.Builder()
|
||||
|
@ -43,6 +46,9 @@ class TestJob(
|
|||
.build()
|
||||
setProgressAsync(progress)
|
||||
}
|
||||
return Result.success()
|
||||
|
||||
val result = Result.success()
|
||||
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,12 @@
|
|||
package com.nextcloud.client.preferences;
|
||||
|
||||
import com.nextcloud.appReview.AppReviewShownModel;
|
||||
import com.nextcloud.client.jobs.LogEntry;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.utils.FileSortOrder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -317,6 +320,12 @@ public interface AppPreferences {
|
|||
*/
|
||||
int getLastSeenVersionCode();
|
||||
|
||||
void saveLogEntry(List<LogEntry> logEntryList);
|
||||
|
||||
List<LogEntry> readLogEntry();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Saves the version code as the last seen version code.
|
||||
*
|
||||
|
|
|
@ -28,11 +28,13 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import com.nextcloud.appReview.AppReviewShownModel;
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
import com.nextcloud.client.account.UserAccountManagerImpl;
|
||||
import com.nextcloud.client.jobs.LogEntry;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
|
@ -41,6 +43,8 @@ import com.owncloud.android.ui.activity.PassCodeActivity;
|
|||
import com.owncloud.android.ui.activity.SettingsActivity;
|
||||
import com.owncloud.android.utils.FileSortOrder;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
|
@ -49,6 +53,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import static com.owncloud.android.ui.fragment.OCFileListFragment.FOLDER_LAYOUT_LIST;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Implementation of application-wide preferences using {@link SharedPreferences}.
|
||||
|
@ -108,6 +113,8 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
private static final String PREF__STORAGE_PERMISSION_REQUESTED = "storage_permission_requested";
|
||||
private static final String PREF__IN_APP_REVIEW_DATA = "in_app_review_data";
|
||||
|
||||
private static final String LOG_ENTRY = "log_entry";
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences preferences;
|
||||
private final UserAccountManager userAccountManager;
|
||||
|
@ -499,6 +506,22 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
return preferences.getInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveLogEntry(List<LogEntry> logEntryList) {
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(logEntryList);
|
||||
preferences.edit().putString(LOG_ENTRY, json).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogEntry> readLogEntry() {
|
||||
String json = preferences.getString(LOG_ENTRY, null);
|
||||
if (json == null) return emptyList();
|
||||
Gson gson = new Gson();
|
||||
Type listType = new TypeToken<List<LogEntry>>() {}.getType();
|
||||
return gson.fromJson(json, listType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastSeenVersionCode(int versionCode) {
|
||||
preferences.edit().putInt(AUTO_PREF__LAST_SEEN_VERSION_CODE, versionCode).apply();
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Alper Ozturk
|
||||
* Copyright (C) 2023 Alper Ozturk
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.utils.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Outline
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
|
||||
fun createRoundedOutline(context: Context, cornerRadiusValue: Float): ViewOutlineProvider {
|
||||
return object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
val left = 0
|
||||
val top = 0
|
||||
val right = view.width
|
||||
val bottom = view.height
|
||||
val cornerRadius = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
cornerRadiusValue,
|
||||
context.resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
outline.setRoundRect(left, top, right, bottom, cornerRadius.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -129,7 +129,6 @@ import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFER
|
|||
* Contains methods to build the "static" strings. These strings were before constants in different classes
|
||||
*/
|
||||
public class MainApp extends MultiDexApplication implements HasAndroidInjector {
|
||||
|
||||
public static final OwnCloudVersion OUTDATED_SERVER_VERSION = NextcloudVersion.nextcloud_23;
|
||||
public static final OwnCloudVersion MINIMUM_SUPPORTED_SERVER_VERSION = OwnCloudVersion.nextcloud_16;
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
|||
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.resources.files.FileUtils;
|
||||
import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
|
||||
import com.owncloud.android.operations.UploadFileOperation;
|
||||
import com.owncloud.android.ui.activity.ConflictsResolveActivity;
|
||||
import com.owncloud.android.ui.activity.UploadListActivity;
|
||||
|
@ -902,12 +903,10 @@ public class FileUploader extends Service
|
|||
* Upload a new file
|
||||
*/
|
||||
public static void uploadNewFile(
|
||||
Context context,
|
||||
User user,
|
||||
String localPath,
|
||||
String remotePath,
|
||||
int behaviour,
|
||||
String mimeType,
|
||||
boolean createRemoteFile,
|
||||
int createdBy,
|
||||
boolean requiresWifi,
|
||||
|
@ -915,11 +914,9 @@ public class FileUploader extends Service
|
|||
NameCollisionPolicy nameCollisionPolicy
|
||||
) {
|
||||
uploadNewFile(
|
||||
context,
|
||||
user,
|
||||
new String[]{localPath},
|
||||
new String[]{remotePath},
|
||||
new String[]{mimeType},
|
||||
behaviour,
|
||||
createRemoteFile,
|
||||
createdBy,
|
||||
|
@ -933,11 +930,9 @@ public class FileUploader extends Service
|
|||
* Upload multiple new files
|
||||
*/
|
||||
public static void uploadNewFile(
|
||||
Context context,
|
||||
User user,
|
||||
String[] localPaths,
|
||||
String[] remotePaths,
|
||||
String[] mimeTypes,
|
||||
Integer behaviour,
|
||||
Boolean createRemoteFolder,
|
||||
int createdBy,
|
||||
|
@ -945,39 +940,15 @@ public class FileUploader extends Service
|
|||
boolean requiresCharging,
|
||||
NameCollisionPolicy nameCollisionPolicy
|
||||
) {
|
||||
|
||||
|
||||
if (useFilesUploadWorker(context)) {
|
||||
new FilesUploadHelper().uploadNewFiles(user,
|
||||
localPaths,
|
||||
remotePaths,
|
||||
createRemoteFolder,
|
||||
createdBy,
|
||||
requiresWifi,
|
||||
requiresCharging,
|
||||
nameCollisionPolicy,
|
||||
behaviour);
|
||||
} else {
|
||||
Intent intent = new Intent(context, FileUploader.class);
|
||||
|
||||
intent.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount());
|
||||
intent.putExtra(FileUploader.KEY_USER, user);
|
||||
intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
|
||||
intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
|
||||
intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
|
||||
intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
|
||||
intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
|
||||
intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
|
||||
intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
|
||||
intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
|
||||
intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
new FilesUploadHelper().uploadNewFiles(user,
|
||||
localPaths,
|
||||
remotePaths,
|
||||
createRemoteFolder,
|
||||
createdBy,
|
||||
requiresWifi,
|
||||
requiresCharging,
|
||||
nameCollisionPolicy,
|
||||
behaviour);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1006,8 +977,7 @@ public class FileUploader extends Service
|
|||
OCFile existingFile,
|
||||
Integer behaviour,
|
||||
NameCollisionPolicy nameCollisionPolicy,
|
||||
boolean disableRetries
|
||||
) {
|
||||
boolean disableRetries) {
|
||||
uploadUpdateFile(context,
|
||||
user,
|
||||
new OCFile[]{existingFile},
|
||||
|
@ -1036,13 +1006,7 @@ public class FileUploader extends Service
|
|||
intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
|
||||
intent.putExtra(FileUploader.KEY_DISABLE_RETRIES, disableRetries);
|
||||
|
||||
if (useFilesUploadWorker(context)) {
|
||||
new FilesUploadHelper().uploadUpdatedFile(user, existingFiles, behaviour, nameCollisionPolicy);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
new FilesUploadHelper().uploadUpdatedFile(user, existingFiles, behaviour, nameCollisionPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1057,13 +1021,7 @@ public class FileUploader extends Service
|
|||
i.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount());
|
||||
i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
|
||||
|
||||
if (useFilesUploadWorker(context)) {
|
||||
new FilesUploadHelper().retryUpload(upload, user);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(i);
|
||||
} else {
|
||||
context.startService(i);
|
||||
}
|
||||
new FilesUploadHelper().retryUpload(upload, user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1129,16 +1087,6 @@ public class FileUploader extends Service
|
|||
return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
|
||||
}
|
||||
|
||||
|
||||
private static boolean useFilesUploadWorker(Context context) {
|
||||
if (forceNewUploadWorker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// bump min version down with every release until minSDK is reached, at that point get rid of old upload code
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || context.getResources().getBoolean(R.bool.is_beta);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setForceNewUploadWorker(final Boolean value) {
|
||||
forceNewUploadWorker = value;
|
||||
|
@ -1150,7 +1098,6 @@ public class FileUploader extends Service
|
|||
* It provides by itself the available operations.
|
||||
*/
|
||||
public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
|
||||
|
||||
/**
|
||||
* Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
|
||||
*/
|
||||
|
@ -1162,7 +1109,7 @@ public class FileUploader extends Service
|
|||
* @param account ownCloud account where the remote file will be stored.
|
||||
* @param file A file in the queue of pending uploads
|
||||
*/
|
||||
public void cancel(Account account, OCFile file) {
|
||||
public void cancel(Account account, ServerFileInterface file) {
|
||||
cancel(account.name, file.getRemotePath(), null);
|
||||
}
|
||||
|
||||
|
@ -1183,34 +1130,10 @@ public class FileUploader extends Service
|
|||
* @param resultCode Setting result code will pause rather than cancel the job
|
||||
*/
|
||||
public void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode) {
|
||||
// Cancel for Android version >= Android 11
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
try {
|
||||
new FilesUploadHelper().cancelFileUpload(remotePath, accountManager.getUser(accountName).get());
|
||||
} catch (NoSuchElementException e) {
|
||||
Log_OC.e(TAG, "Error cancelling current upload because user does not exist!");
|
||||
}
|
||||
} else {
|
||||
// Cancel for Android version <= Android 10
|
||||
Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(accountName, remotePath);
|
||||
UploadFileOperation upload = removeResult.first;
|
||||
if (upload == null && mCurrentUpload != null && mCurrentAccount != null &&
|
||||
mCurrentUpload.getRemotePath().startsWith(remotePath) && accountName.equals(mCurrentAccount.name)) {
|
||||
|
||||
upload = mCurrentUpload;
|
||||
}
|
||||
|
||||
if (upload != null) {
|
||||
upload.cancel(resultCode);
|
||||
// need to update now table in mUploadsStorageManager,
|
||||
// since the operation will not get to be run by FileUploader#uploadFile
|
||||
if (resultCode != null) {
|
||||
mUploadsStorageManager.updateDatabaseUploadResult(new RemoteOperationResult(resultCode), upload);
|
||||
notifyUploadResult(upload, new RemoteOperationResult(resultCode));
|
||||
} else {
|
||||
mUploadsStorageManager.removeUpload(accountName, remotePath);
|
||||
}
|
||||
}
|
||||
try {
|
||||
new FilesUploadHelper().cancelFileUpload(remotePath, accountManager.getUser(accountName).get());
|
||||
} catch (NoSuchElementException e) {
|
||||
Log_OC.e(TAG, "Error cancelling current upload because user does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1225,14 +1148,7 @@ public class FileUploader extends Service
|
|||
|
||||
public void cancel(String accountName) {
|
||||
cancelPendingUploads(accountName);
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
new FilesUploadHelper().restartUploadJob(accountManager.getUser(accountName).get());
|
||||
} else {
|
||||
if (mCurrentUpload != null && mCurrentUpload.getUser().nameEquals(accountName)) {
|
||||
mCurrentUpload.cancel(ResultCode.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
new FilesUploadHelper().restartUploadJob(accountManager.getUser(accountName).get());
|
||||
}
|
||||
|
||||
public void clearListeners() {
|
||||
|
@ -1256,43 +1172,27 @@ public class FileUploader extends Service
|
|||
if (user == null || file == null) {
|
||||
return false;
|
||||
}
|
||||
if (useFilesUploadWorker(getApplicationContext())){
|
||||
// Not same as for service because upload list is "created" on the spot in the worker and not available here
|
||||
|
||||
OCUpload upload = mUploadsStorageManager.getUploadByRemotePath(file.getRemotePath());
|
||||
if (upload == null){
|
||||
return false;
|
||||
}
|
||||
return upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS;
|
||||
OCUpload upload = mUploadsStorageManager.getUploadByRemotePath(file.getRemotePath());
|
||||
|
||||
}else{
|
||||
return mPendingUploads.contains(user.getAccountName(), file.getRemotePath());
|
||||
if (upload == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
return upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS;
|
||||
}
|
||||
|
||||
@SuppressFBWarnings("NP")
|
||||
public boolean isUploadingNow(OCUpload upload) {
|
||||
if (useFilesUploadWorker(getApplicationContext())){
|
||||
UploadFileOperation currentUploadFileOperation = FilesUploadWorker.Companion.getCurrentUploadFileOperation();
|
||||
if (currentUploadFileOperation == null || currentUploadFileOperation.getUser() == null) return false;
|
||||
if (upload == null || (!upload.getAccountName().equals(currentUploadFileOperation.getUser().getAccountName()))) return false;
|
||||
if (currentUploadFileOperation.getOldFile() != null){
|
||||
// For file conflicts check old file remote path
|
||||
return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath()) ||
|
||||
upload.getRemotePath().equals(currentUploadFileOperation.getOldFile().getRemotePath());
|
||||
}
|
||||
return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath());
|
||||
|
||||
}else {
|
||||
|
||||
return upload != null &&
|
||||
mCurrentAccount != null &&
|
||||
mCurrentUpload != null &&
|
||||
upload.getAccountName().equals(mCurrentAccount.name) &&
|
||||
(upload.getRemotePath().equals(mCurrentUpload.getRemotePath()) ||
|
||||
(mCurrentUpload.getOldFile() != null &&
|
||||
upload.getRemotePath().equals(mCurrentUpload.getOldFile().getRemotePath())));
|
||||
UploadFileOperation currentUploadFileOperation = FilesUploadWorker.Companion.getCurrentUploadFileOperation();
|
||||
if (currentUploadFileOperation == null || currentUploadFileOperation.getUser() == null) return false;
|
||||
if (upload == null || (!upload.getAccountName().equals(currentUploadFileOperation.getUser().getAccountName()))) return false;
|
||||
if (currentUploadFileOperation.getOldFile() != null){
|
||||
// For file conflicts check old file remote path
|
||||
return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath()) ||
|
||||
upload.getRemotePath().equals(currentUploadFileOperation.getOldFile().getRemotePath());
|
||||
}
|
||||
return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1305,18 +1205,14 @@ public class FileUploader extends Service
|
|||
public void addDatatransferProgressListener(
|
||||
OnDatatransferProgressListener listener,
|
||||
User user,
|
||||
OCFile file
|
||||
ServerFileInterface file
|
||||
) {
|
||||
if (user == null || file == null || listener == null) {
|
||||
return;
|
||||
}
|
||||
String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
|
||||
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
|
||||
}else {
|
||||
mBoundListeners.put(targetKey, listener);
|
||||
}
|
||||
String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
|
||||
new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1334,11 +1230,7 @@ public class FileUploader extends Service
|
|||
}
|
||||
|
||||
String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
|
||||
}else {
|
||||
mBoundListeners.put(targetKey, listener);
|
||||
}
|
||||
new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1351,21 +1243,14 @@ public class FileUploader extends Service
|
|||
public void removeDatatransferProgressListener(
|
||||
OnDatatransferProgressListener listener,
|
||||
User user,
|
||||
OCFile file
|
||||
ServerFileInterface file
|
||||
) {
|
||||
if (user == null || file == null || listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
|
||||
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
|
||||
}else {
|
||||
if (mBoundListeners.get(targetKey) == listener) {
|
||||
mBoundListeners.remove(targetKey);
|
||||
}
|
||||
}
|
||||
new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1383,14 +1268,7 @@ public class FileUploader extends Service
|
|||
}
|
||||
|
||||
String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
|
||||
|
||||
if (useFilesUploadWorker(getApplicationContext())) {
|
||||
new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
|
||||
}else {
|
||||
if (mBoundListeners.get(targetKey) == listener) {
|
||||
mBoundListeners.remove(targetKey);
|
||||
}
|
||||
}
|
||||
new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -906,8 +906,15 @@ public class FileDisplayActivity extends FileActivity implements FileFragment.Co
|
|||
default -> FileUploader.LOCAL_BEHAVIOUR_FORGET;
|
||||
};
|
||||
|
||||
FileUploader.uploadNewFile(this, getUser().orElseThrow(RuntimeException::new), filePaths, remotePaths, null, // MIME type will be detected from file name
|
||||
behaviour, true, UploadFileOperation.CREATED_BY_USER, false, false, NameCollisionPolicy.ASK_USER);
|
||||
FileUploader.uploadNewFile(getUser().orElseThrow(RuntimeException::new),
|
||||
filePaths,
|
||||
remotePaths,
|
||||
behaviour,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
false,
|
||||
NameCollisionPolicy.ASK_USER);
|
||||
|
||||
} else {
|
||||
Log_OC.d(TAG, "User clicked on 'Update' with no selection");
|
||||
|
@ -1471,6 +1478,7 @@ public class FileDisplayActivity extends FileActivity implements FileFragment.Co
|
|||
setFile(listOfFiles.getCurrentFile());
|
||||
startSyncFolderOperation(root, false);
|
||||
}
|
||||
binding.fabMain.setImageResource(R.drawable.ic_plus);
|
||||
resetTitleBarAndScrolling();
|
||||
}
|
||||
|
||||
|
|
|
@ -885,12 +885,10 @@ public class ReceiveExternalFilesActivity extends FileActivity
|
|||
|
||||
public void uploadFile(String tmpName, String filename) {
|
||||
FileUploader.uploadNewFile(
|
||||
getBaseContext(),
|
||||
getUser().orElseThrow(RuntimeException::new),
|
||||
tmpName,
|
||||
mFile.getRemotePath() + filename,
|
||||
FileUploader.LOCAL_BEHAVIOUR_COPY,
|
||||
null,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
@ -196,19 +196,22 @@ public class UploadListActivity extends FileActivity {
|
|||
private void refresh() {
|
||||
backgroundJobManager.startImmediateFilesSyncJob(false, true);
|
||||
|
||||
// retry failed uploads
|
||||
new Thread(() -> FileUploader.retryFailedUploads(
|
||||
this,
|
||||
uploadsStorageManager,
|
||||
connectivityService,
|
||||
userAccountManager,
|
||||
powerManagementService))
|
||||
.start();
|
||||
if(uploadsStorageManager.getFailedUploads().length > 0){
|
||||
// retry failed uploads
|
||||
new Thread(() -> FileUploader.retryFailedUploads(
|
||||
this,
|
||||
uploadsStorageManager,
|
||||
connectivityService,
|
||||
userAccountManager,
|
||||
powerManagementService))
|
||||
.start();
|
||||
DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
|
||||
}
|
||||
|
||||
|
||||
// update UI
|
||||
uploadListAdapter.loadUploadItemsFromDb();
|
||||
swipeListRefreshLayout.setRefreshing(false);
|
||||
DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
|
||||
|
@ -32,12 +34,14 @@ interface ListGridImageViewHolder {
|
|||
val shimmerThumbnail: LoaderImageView
|
||||
val favorite: ImageView
|
||||
val localFileIndicator: ImageView
|
||||
val imageFileName: TextView?
|
||||
val shared: ImageView
|
||||
val checkbox: ImageView
|
||||
val itemLayout: View
|
||||
val unreadComments: ImageView
|
||||
|
||||
val gridLivePhotoIndicator: TextView?
|
||||
val more: ImageButton?
|
||||
val fileFeaturesLayout: LinearLayout?
|
||||
val gridLivePhotoIndicator: ImageView?
|
||||
val livePhotoIndicator: TextView?
|
||||
val livePhotoIndicatorSeparator: TextView?
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import android.widget.ImageView;
|
|||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
||||
import com.nextcloud.client.preferences.AppPreferences;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||
|
@ -54,6 +55,7 @@ import java.util.concurrent.Executors;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
|
@ -186,11 +188,10 @@ public class LocalFileListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
} else {
|
||||
gridViewHolder.checkbox.setVisibility(View.VISIBLE);
|
||||
if (isCheckedFile(file)) {
|
||||
gridViewHolder.itemLayout.setBackgroundColor(mContext.getResources()
|
||||
.getColor(R.color.selected_item_background));
|
||||
gridViewHolder.itemLayout.setBackgroundColor(ContextCompat.getColor(mContext, R.color.selected_item_background));
|
||||
|
||||
gridViewHolder.checkbox.setImageDrawable(
|
||||
viewThemeUtils.platform.tintPrimaryDrawable(mContext, R.drawable.ic_checkbox_marked));
|
||||
viewThemeUtils.platform.tintDrawable(mContext, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY));
|
||||
} else {
|
||||
gridViewHolder.itemLayout.setBackgroundColor(mContext.getResources().getColor(R.color.bg_default));
|
||||
gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_blank_outline);
|
||||
|
|
|
@ -39,7 +39,9 @@ import android.text.TextUtils;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView;
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
||||
|
@ -300,6 +302,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
return headerId;
|
||||
}
|
||||
|
||||
|
||||
// skip header
|
||||
position--;
|
||||
}
|
||||
|
@ -365,8 +368,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEWTYPE_ITEM:
|
||||
default -> {
|
||||
if (gridView) {
|
||||
return new OCFileListGridItemViewHolder(
|
||||
GridItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
|
||||
|
@ -376,8 +378,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
ListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
|
||||
);
|
||||
}
|
||||
|
||||
case VIEWTYPE_IMAGE:
|
||||
}
|
||||
case VIEWTYPE_IMAGE -> {
|
||||
if (gridView) {
|
||||
return new OCFileListGridImageViewHolder(
|
||||
GridImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
|
||||
|
@ -387,23 +389,22 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
ListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
|
||||
);
|
||||
}
|
||||
|
||||
case VIEWTYPE_FOOTER:
|
||||
}
|
||||
case VIEWTYPE_FOOTER -> {
|
||||
return new OCFileListFooterViewHolder(
|
||||
ListFooterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)
|
||||
);
|
||||
|
||||
case VIEWTYPE_HEADER:
|
||||
}
|
||||
case VIEWTYPE_HEADER -> {
|
||||
ListHeaderBinding binding = ListHeaderBinding.inflate(
|
||||
LayoutInflater.from(parent.getContext()),
|
||||
parent,
|
||||
false);
|
||||
|
||||
ViewGroup.LayoutParams layoutParams = binding.headerView.getLayoutParams();
|
||||
layoutParams.height = (int) (parent.getHeight() * 0.3);
|
||||
binding.headerView.setLayoutParams(layoutParams);
|
||||
|
||||
return new OCFileListHeaderViewHolder(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,6 +431,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
}
|
||||
|
||||
ocFileListDelegate.bindGridViewHolder(gridViewHolder, file, searchType);
|
||||
checkVisibilityOfMoreButtons(gridViewHolder);
|
||||
checkVisibilityOfFileFeaturesLayout(gridViewHolder);
|
||||
|
||||
if (holder instanceof ListItemViewHolder) {
|
||||
bindListItemViewHolder((ListItemViewHolder) gridViewHolder, file);
|
||||
|
@ -437,12 +440,45 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
|
||||
if (holder instanceof ListGridItemViewHolder) {
|
||||
bindListGridItemViewHolder((ListGridItemViewHolder) holder, file);
|
||||
checkVisibilityOfMoreButtons((ListGridItemViewHolder) holder);
|
||||
checkVisibilityOfFileFeaturesLayout((ListGridItemViewHolder) holder);
|
||||
}
|
||||
|
||||
updateLivePhotoIndicators(gridViewHolder, file);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVisibilityOfFileFeaturesLayout(ListGridImageViewHolder holder) {
|
||||
int fileFeaturesVisibility = View.GONE;
|
||||
LinearLayout fileFeaturesLayout = holder.getFileFeaturesLayout();
|
||||
|
||||
if (fileFeaturesLayout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < fileFeaturesLayout.getChildCount(); i++) {
|
||||
View child = fileFeaturesLayout.getChildAt(i);
|
||||
if (child.getVisibility() == View.VISIBLE) {
|
||||
fileFeaturesVisibility = View.VISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
fileFeaturesLayout.setVisibility(fileFeaturesVisibility);
|
||||
}
|
||||
|
||||
private void checkVisibilityOfMoreButtons(ListGridImageViewHolder holder) {
|
||||
ImageButton moreButton = holder.getMore();
|
||||
if (moreButton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMultiSelect()) {
|
||||
moreButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
moreButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeOCFilesForLivePhoto() {
|
||||
List<OCFile> filesToRemove = new ArrayList<>();
|
||||
|
||||
|
|
|
@ -27,10 +27,13 @@ import android.graphics.drawable.ColorDrawable
|
|||
import android.os.AsyncTask
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.nextcloud.utils.extensions.createRoundedOutline
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
|
@ -142,7 +145,7 @@ class OCFileListDelegate(
|
|||
storageManager,
|
||||
asyncGalleryTasks,
|
||||
file.remoteId,
|
||||
context.resources.getColor(R.color.bg_default)
|
||||
ContextCompat.getColor(context, R.color.bg_default)
|
||||
)
|
||||
var drawable = MimeTypeUtil.getFileTypeIcon(
|
||||
file.mimeType,
|
||||
|
@ -204,6 +207,7 @@ class OCFileListDelegate(
|
|||
searchType: SearchType?
|
||||
) {
|
||||
// thumbnail
|
||||
gridViewHolder.imageFileName?.text = file.fileName
|
||||
gridViewHolder.thumbnail.tag = file.fileId
|
||||
DisplayUtils.setThumbnail(
|
||||
file,
|
||||
|
@ -218,6 +222,7 @@ class OCFileListDelegate(
|
|||
viewThemeUtils,
|
||||
syncFolderProvider
|
||||
)
|
||||
|
||||
// item layout + click listeners
|
||||
bindGridItemLayout(file, gridViewHolder)
|
||||
|
||||
|
@ -232,17 +237,20 @@ class OCFileListDelegate(
|
|||
}
|
||||
|
||||
// download state
|
||||
gridViewHolder.localFileIndicator.visibility = View.INVISIBLE // default first
|
||||
gridViewHolder.localFileIndicator.visibility = View.GONE // default first
|
||||
|
||||
// metadata (downloaded, favorite)
|
||||
bindGridMetadataViews(file, gridViewHolder)
|
||||
|
||||
// shares
|
||||
val shouldHideShare = gridView ||
|
||||
val shouldHideShare = (
|
||||
hideItemOptions ||
|
||||
!file.isFolder && file.isEncrypted ||
|
||||
file.isEncrypted && !EncryptionUtils.supportsSecureFiledrop(file, user) ||
|
||||
searchType == SearchType.FAVORITE_SEARCH
|
||||
!file.isFolder &&
|
||||
file.isEncrypted ||
|
||||
file.isEncrypted &&
|
||||
!EncryptionUtils.supportsSecureFiledrop(file, user) ||
|
||||
searchType == SearchType.FAVORITE_SEARCH
|
||||
)
|
||||
if (shouldHideShare) {
|
||||
gridViewHolder.shared.visibility = View.GONE
|
||||
} else {
|
||||
|
@ -263,32 +271,61 @@ class OCFileListDelegate(
|
|||
}
|
||||
|
||||
private fun bindGridItemLayout(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
|
||||
if (highlightedItem != null && file.fileId == highlightedItem!!.fileId) {
|
||||
gridViewHolder.itemLayout.setBackgroundColor(
|
||||
context.resources
|
||||
.getColor(R.color.selected_item_background)
|
||||
)
|
||||
} else if (isCheckedFile(file)) {
|
||||
gridViewHolder.itemLayout.setBackgroundColor(
|
||||
context.resources
|
||||
.getColor(R.color.selected_item_background)
|
||||
)
|
||||
setItemLayoutBackgroundColor(file, gridViewHolder)
|
||||
setCheckBoxImage(file, gridViewHolder)
|
||||
setItemLayoutOnClickListeners(file, gridViewHolder)
|
||||
|
||||
gridViewHolder.more?.setOnClickListener {
|
||||
ocFileListFragmentInterface.onOverflowIconClicked(file, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setItemLayoutOnClickListeners(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
|
||||
gridViewHolder.itemLayout.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) }
|
||||
|
||||
if (!hideItemOptions) {
|
||||
gridViewHolder.itemLayout.apply {
|
||||
isLongClickable = true
|
||||
setOnLongClickListener {
|
||||
ocFileListFragmentInterface.onLongItemClicked(
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setItemLayoutBackgroundColor(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
|
||||
val cornerRadius = context.resources.getDimension(R.dimen.selected_grid_container_radius)
|
||||
|
||||
val isDarkModeActive = (syncFolderProvider?.preferences?.isDarkModeEnabled == true)
|
||||
val selectedItemBackgroundColorId: Int = if (isDarkModeActive) {
|
||||
R.color.action_mode_background
|
||||
} else {
|
||||
R.color.selected_item_background
|
||||
}
|
||||
|
||||
val itemLayoutBackgroundColorId: Int = if (file.fileId == highlightedItem?.fileId || isCheckedFile(file)) {
|
||||
selectedItemBackgroundColorId
|
||||
} else {
|
||||
R.color.bg_default
|
||||
}
|
||||
|
||||
gridViewHolder.itemLayout.apply {
|
||||
outlineProvider = createRoundedOutline(context, cornerRadius)
|
||||
clipToOutline = true
|
||||
setBackgroundColor(ContextCompat.getColor(context, itemLayoutBackgroundColorId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCheckBoxImage(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
|
||||
if (isCheckedFile(file)) {
|
||||
gridViewHolder.checkbox.setImageDrawable(
|
||||
viewThemeUtils.platform.tintPrimaryDrawable(context, R.drawable.ic_checkbox_marked)
|
||||
viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY)
|
||||
)
|
||||
} else {
|
||||
gridViewHolder.itemLayout.setBackgroundColor(context.resources.getColor(R.color.bg_default))
|
||||
gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_blank_outline)
|
||||
}
|
||||
gridViewHolder.itemLayout.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) }
|
||||
if (!hideItemOptions) {
|
||||
gridViewHolder.itemLayout.isLongClickable = true
|
||||
gridViewHolder.itemLayout.setOnLongClickListener {
|
||||
ocFileListFragmentInterface.onLongItemClicked(
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindGridMetadataViews(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
|
@ -33,9 +35,13 @@ internal class OCFileListGridImageViewHolder(var binding: GridImageBinding) :
|
|||
binding.root
|
||||
),
|
||||
ListGridImageViewHolder {
|
||||
|
||||
override val thumbnail: ImageView
|
||||
get() = binding.thumbnail
|
||||
|
||||
override val imageFileName: TextView
|
||||
get() = binding.Filename
|
||||
|
||||
override fun showVideoOverlay() {
|
||||
// noop
|
||||
}
|
||||
|
@ -54,8 +60,11 @@ internal class OCFileListGridImageViewHolder(var binding: GridImageBinding) :
|
|||
get() = binding.ListItemLayout
|
||||
override val unreadComments: ImageView
|
||||
get() = binding.unreadComments
|
||||
|
||||
override val gridLivePhotoIndicator: TextView
|
||||
override val more: ImageButton
|
||||
get() = binding.more
|
||||
override val fileFeaturesLayout: LinearLayout
|
||||
get() = binding.fileFeaturesLayout
|
||||
override val gridLivePhotoIndicator: ImageView
|
||||
get() = binding.gridLivePhotoIndicator
|
||||
override val livePhotoIndicator: TextView?
|
||||
get() = null
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
|
@ -48,6 +50,8 @@ internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
|
|||
get() = binding.favoriteAction
|
||||
override val localFileIndicator: ImageView
|
||||
get() = binding.localFileIndicator
|
||||
override val imageFileName: TextView?
|
||||
get() = null
|
||||
override val shared: ImageView
|
||||
get() = binding.sharedIcon
|
||||
override val checkbox: ImageView
|
||||
|
@ -57,12 +61,16 @@ internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
|
|||
override val unreadComments: ImageView
|
||||
get() = binding.unreadComments
|
||||
|
||||
override val gridLivePhotoIndicator: TextView?
|
||||
override val gridLivePhotoIndicator: ImageView?
|
||||
get() = null
|
||||
override val livePhotoIndicator: TextView?
|
||||
get() = null
|
||||
override val livePhotoIndicatorSeparator: TextView?
|
||||
get() = null
|
||||
override val fileFeaturesLayout: LinearLayout
|
||||
get() = binding.fileFeaturesLayout
|
||||
override val more: ImageButton
|
||||
get() = binding.more
|
||||
|
||||
init {
|
||||
binding.favoriteAction.drawable.mutate()
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
package com.owncloud.android.ui.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
|
@ -37,7 +38,7 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
|
|||
binding.root
|
||||
),
|
||||
ListItemViewHolder {
|
||||
override val gridLivePhotoIndicator: TextView?
|
||||
override val gridLivePhotoIndicator: ImageView?
|
||||
get() = null
|
||||
override val livePhotoIndicator: TextView
|
||||
get() = binding.livePhotoIndicator
|
||||
|
@ -73,12 +74,18 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
|
|||
binding.thumbnailLayout.videoOverlay.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override val more: ImageButton?
|
||||
get() = null
|
||||
override val fileFeaturesLayout: LinearLayout?
|
||||
get() = null
|
||||
override val shimmerThumbnail: LoaderImageView
|
||||
get() = binding.thumbnailLayout.thumbnailShimmer
|
||||
override val favorite: ImageView
|
||||
get() = binding.favoriteAction
|
||||
override val localFileIndicator: ImageView
|
||||
get() = binding.localFileIndicator
|
||||
override val imageFileName: TextView?
|
||||
get() = null
|
||||
override val shared: ImageView
|
||||
get() = binding.sharedIcon
|
||||
override val checkbox: ImageView
|
||||
|
|
|
@ -192,8 +192,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
|||
user,
|
||||
fullTempPath,
|
||||
currentRemotePath,
|
||||
behaviour,
|
||||
leakedContentResolver.getType(currentUri)
|
||||
behaviour
|
||||
);
|
||||
fullTempPath = null;
|
||||
}
|
||||
|
@ -247,14 +246,12 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
|
|||
return result;
|
||||
}
|
||||
|
||||
private void requestUpload(User user, String localPath, String remotePath, int behaviour, String mimeType) {
|
||||
private void requestUpload(User user, String localPath, String remotePath, int behaviour) {
|
||||
FileUploader.uploadNewFile(
|
||||
mAppContext,
|
||||
user,
|
||||
localPath,
|
||||
remotePath,
|
||||
behaviour,
|
||||
mimeType,
|
||||
false, // do not create parent folder if not existent
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
@ -126,12 +126,10 @@ class UriUploader(
|
|||
*/
|
||||
private fun requestUpload(localPath: String?, remotePath: String) {
|
||||
FileUploader.uploadNewFile(
|
||||
mActivity,
|
||||
user,
|
||||
localPath,
|
||||
remotePath,
|
||||
mBehaviour,
|
||||
null, // MIME type will be detected from file name
|
||||
false, // do not create parent folder if not existent
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
@ -977,9 +977,9 @@ public final class DisplayUtils {
|
|||
}
|
||||
|
||||
private static void configShimmerGridImageSize(LoaderImageView thumbnailShimmer, float gridColumns) {
|
||||
FrameLayout.LayoutParams targetLayoutParams = (FrameLayout.LayoutParams) thumbnailShimmer.getLayoutParams();
|
||||
|
||||
try {
|
||||
FrameLayout.LayoutParams targetLayoutParams = (FrameLayout.LayoutParams) thumbnailShimmer.getLayoutParams();
|
||||
|
||||
final Point screenSize = getScreenSize(thumbnailShimmer.getContext());
|
||||
final int marginLeftAndRight = targetLayoutParams.leftMargin + targetLayoutParams.rightMargin;
|
||||
final int size = Math.round(screenSize.x / gridColumns - marginLeftAndRight);
|
||||
|
|
|
@ -151,7 +151,6 @@ class FilesUploadHelper {
|
|||
val boundListener = mBoundListeners[key]
|
||||
|
||||
boundListener?.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName)
|
||||
Log_OC.d("TAG", "Hello")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,4 +136,48 @@
|
|||
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:id="@+id/etm_background_execution_times_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:text="@string/etm_background_execution_count" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/etm_background_execution_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="0" />
|
||||
|
||||
</TableRow>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TableRow
|
||||
android:id="@+id/etm_background_execution_logs_row"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="horizontal"
|
||||
android:scrollHorizontally="true">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/etm_background_execution_logs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_span="2" />
|
||||
|
||||
|
||||
</TableRow>
|
||||
</HorizontalScrollView>
|
||||
|
||||
|
||||
</TableLayout>
|
||||
|
|
|
@ -15,129 +15,176 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<LinearLayout
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/ListItemLayout"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="@dimen/grid_container_margin"
|
||||
android:layout_marginTop="@dimen/grid_container_margin"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="@dimen/grid_container_height">
|
||||
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/grid_image_icon_margin"
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone"
|
||||
app:corners="6"
|
||||
app:height_weight="0.6"
|
||||
app:width_weight="0.4" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="@dimen/grid_container_width"
|
||||
android:layout_height="@dimen/grid_container_height">
|
||||
|
||||
<com.owncloud.android.ui.SquareImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:padding="@dimen/grid_image_icon_padding"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/file_image" />
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="@dimen/grid_thumbnail_margin_bottom"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/favorite_action"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/favorite_icon"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/favorite" />
|
||||
|
||||
<ImageView
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/unreadComments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="70dp"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/unread_comments"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_comment_grid"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/localFileIndicator"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginTop="@dimen/standard_quarter_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:layout_marginBottom="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/synced_icon"
|
||||
android:src="@drawable/ic_synced" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="#F6F6F6">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedIcon"
|
||||
<com.owncloud.android.ui.SquareImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_marginBottom="@dimen/grid_thumbnail_margin_bottom"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:scaleType="centerCrop"
|
||||
tools:visibility="visible"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/file_image" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/file_features_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/shared_icon_shared_via_link"
|
||||
android:src="@drawable/shared_via_link" />
|
||||
android:gravity="center"
|
||||
android:layout_marginEnd="@dimen/grid_layout_file_features_margin_end"
|
||||
android:layout_marginBottom="@dimen/grid_layout_margin_bottom"
|
||||
android:alpha="0.9"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="@color/grid_file_features_background_color"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/standard_quarter_padding"
|
||||
android:translationZ="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/thumbnail"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/favorite_action"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/favorite_icon"
|
||||
android:src="@drawable/favorite" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/grid_live_photo_indicator"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableTint="@color/list_item_lastmod_and_filesize_text"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="#F6F6F6"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="2dp"
|
||||
android:layout_marginLeft="@dimen/standard_quarter_margin"
|
||||
android:layout_marginRight="@dimen/standard_quarter_margin"
|
||||
android:text="@string/file_list_live"
|
||||
android:drawablePadding="@dimen/standard_eight_padding"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="start|bottom"
|
||||
android:textColor="@color/list_item_lastmod_and_filesize_text"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
app:drawableTopCompat="@drawable/ic_live_photo" />
|
||||
<ImageView
|
||||
android:id="@+id/videoOverlay"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/video_overlay_icon"
|
||||
android:src="@drawable/video_white"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/live_photo_indicator_separator"
|
||||
android:visibility="gone"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"/>
|
||||
<ImageView
|
||||
android:id="@+id/sharedIcon"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/shared_icon_shared_via_link"
|
||||
android:src="@drawable/shared_via_link"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/custom_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|top"
|
||||
android:layout_marginLeft="@dimen/standard_quarter_margin"
|
||||
android:layout_marginRight="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/checkbox"
|
||||
android:src="@android:drawable/checkbox_off_background" />
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/unreadComments"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/unread_comments"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_comment_grid"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/grid_live_photo_indicator"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/grid_file_features_live_photo_content_description"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_live_photo"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/localFileIndicator"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/synced_icon"
|
||||
android:src="@drawable/ic_synced"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/custom_checkbox"
|
||||
android:layout_width="@dimen/grid_checkbox_size"
|
||||
android:layout_height="@dimen/grid_checkbox_size"
|
||||
android:layout_marginStart="@dimen/grid_checkbox_margin"
|
||||
android:layout_marginTop="@dimen/grid_checkbox_margin"
|
||||
android:contentDescription="@string/checkbox"
|
||||
android:src="@android:drawable/checkbox_off_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/Filename"
|
||||
android:layout_width="@dimen/grid_filename_width"
|
||||
android:layout_height="@dimen/grid_bottom_view_height"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/grid_bottom_view_margin_bottom"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:text="@string/placeholder_filename"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/grid_item_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/more"
|
||||
android:layout_width="@dimen/grid_bottom_view_height"
|
||||
android:layout_marginBottom="@dimen/grid_bottom_view_margin_bottom"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
android:layout_height="@dimen/grid_bottom_view_height"
|
||||
android:layout_marginEnd="@dimen/grid_bottom_view_margin_end"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/transparent"
|
||||
android:contentDescription="@string/overflow_menu"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/ic_dots_vertical" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -15,130 +15,164 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<com.owncloud.android.ui.SquareLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<LinearLayout
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/ListItemLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginEnd="@dimen/grid_container_margin"
|
||||
android:layout_marginTop="@dimen/grid_container_margin"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/grid_container_height">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_width="@dimen/grid_container_width"
|
||||
android:layout_height="@dimen/grid_container_height">
|
||||
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="@dimen/grid_thumbnail_margin_bottom"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favorite_action"
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:layout_marginBottom="@dimen/grid_thumbnail_margin_bottom"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/file_features_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="@dimen/standard_quarter_margin"
|
||||
android:src="@drawable/favorite"
|
||||
android:contentDescription="@string/favorite_icon"
|
||||
app:layout_constraintEnd_toEndOf="@+id/frameLayout"
|
||||
app:layout_constraintTop_toTopOf="@id/frameLayout"
|
||||
app:layout_constraintBottom_toTopOf="@id/frameLayout"/>
|
||||
android:gravity="center"
|
||||
android:layout_marginEnd="@dimen/grid_layout_file_features_margin_end"
|
||||
android:layout_marginBottom="@dimen/grid_layout_margin_bottom"
|
||||
android:alpha="0.9"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="@color/grid_file_features_background_color"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/standard_quarter_padding"
|
||||
android:translationZ="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/thumbnail"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/frameLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
<ImageView
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/favorite_action"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/favorite_icon"
|
||||
android:src="@drawable/favorite" />
|
||||
|
||||
<com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||
android:id="@+id/thumbnail_shimmer"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:layout_gravity="center"
|
||||
<ImageView
|
||||
android:id="@+id/videoOverlay"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/video_overlay_icon"
|
||||
android:src="@drawable/video_white"
|
||||
android:visibility="gone"
|
||||
app:corners="8" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/standard_list_item_size"
|
||||
android:layout_height="@dimen/standard_list_item_size"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/folder" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/videoOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:contentDescription="@string/video_overlay_icon"
|
||||
android:src="@drawable/video_white"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="@dimen/grid_item_shared_icon_layout_top_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/shared_icon_shared_via_link"
|
||||
android:src="@drawable/shared_via_link" />
|
||||
android:src="@drawable/shared_via_link"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/unreadComments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="@dimen/grid_item_shared_icon_layout_top_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/unread_comments"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_comment_grid"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
app:tint="@color/grid_file_features_icon_color"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/localFileIndicator"
|
||||
android:layout_width="@dimen/grid_item_local_file_indicator_layout_width"
|
||||
android:layout_height="@dimen/grid_item_local_file_indicator_layout_height"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginTop="@dimen/standard_quarter_margin"
|
||||
android:layout_marginEnd="@dimen/standard_quarter_margin"
|
||||
android:layout_marginBottom="@dimen/standard_quarter_margin"
|
||||
android:layout_width="@dimen/grid_layout_item_size"
|
||||
android:layout_height="@dimen/grid_layout_item_size"
|
||||
android:layout_marginEnd="@dimen/grid_layout_margin_end"
|
||||
android:contentDescription="@string/synced_icon"
|
||||
android:src="@drawable/ic_synced" />
|
||||
android:src="@drawable/ic_synced"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/custom_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|top"
|
||||
android:layout_marginLeft="@dimen/standard_quarter_margin"
|
||||
android:layout_marginRight="@dimen/standard_quarter_margin"
|
||||
android:contentDescription="@string/checkbox"
|
||||
android:src="@android:drawable/checkbox_off_background" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/custom_checkbox"
|
||||
android:layout_width="@dimen/grid_checkbox_size"
|
||||
android:layout_height="@dimen/grid_checkbox_size"
|
||||
android:layout_marginStart="@dimen/grid_checkbox_margin"
|
||||
android:layout_marginTop="@dimen/grid_checkbox_margin"
|
||||
android:contentDescription="@string/checkbox"
|
||||
android:src="@android:drawable/checkbox_off_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/Filename"
|
||||
android:layout_width="@dimen/grid_filename_width"
|
||||
android:layout_height="@dimen/grid_bottom_view_height"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/grid_bottom_view_margin_bottom"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:text="@string/placeholder_filename"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/grid_item_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/more"
|
||||
android:layout_width="@dimen/grid_bottom_view_height"
|
||||
android:layout_height="@dimen/grid_bottom_view_height"
|
||||
android:layout_marginBottom="@dimen/grid_bottom_view_margin_bottom"
|
||||
android:layout_marginEnd="@dimen/grid_bottom_view_margin_end"
|
||||
android:translationZ="2dp"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/transparent"
|
||||
android:contentDescription="@string/overflow_menu"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/ic_dots_vertical" />
|
||||
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/Filename"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/standard_quarter_margin"
|
||||
android:layout_marginRight="@dimen/standard_quarter_margin"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="center_horizontal"
|
||||
android:singleLine="true"
|
||||
android:text="@string/placeholder_filename"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/grid_item_text_size" />
|
||||
|
||||
</com.owncloud.android.ui.SquareLinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
<color name="white">#ffffff</color>
|
||||
<color name="white_helper_text">#B3FFFFFF</color>
|
||||
<color name="text_color">#333333</color>
|
||||
<color name="grid_file_features_icon_color">#303034</color>
|
||||
<color name="grid_file_features_background_color">#E9E8EB</color>
|
||||
<color name="drawer_text_color">@color/secondary_text_color</color>
|
||||
<color name="text_color_inverse">#ffffff</color>
|
||||
<color name="disabled_text">#ff888888</color>
|
||||
|
|
|
@ -40,6 +40,11 @@
|
|||
<dimen name="standard_double_margin">32dp</dimen>
|
||||
<dimen name="standard_half_margin">8dp</dimen>
|
||||
<dimen name="standard_quarter_margin">4dp</dimen>
|
||||
<dimen name="grid_layout_item_size">10dp</dimen>
|
||||
<dimen name="grid_layout_file_features_margin_end">24dp</dimen>
|
||||
<dimen name="grid_layout_margin_end">2dp</dimen>
|
||||
<dimen name="grid_layout_margin_bottom">16dp</dimen>
|
||||
<dimen name="grid_sync_item_layout_next_text_size">22sp</dimen>
|
||||
<dimen name="button_width">140dp</dimen>
|
||||
<dimen name="button_extra_width">180dp</dimen>
|
||||
<dimen name="standard_eighth_margin">2dp</dimen>
|
||||
|
@ -103,6 +108,17 @@
|
|||
<dimen name="activity_row_layout_height">48dp</dimen>
|
||||
<dimen name="notification_icon_width">24dp</dimen>
|
||||
<dimen name="notification_icon_height">24dp</dimen>
|
||||
<dimen name="grid_container_margin">4dp</dimen>
|
||||
<dimen name="selected_grid_container_radius">4dp</dimen>
|
||||
<dimen name="grid_checkbox_size">18dp</dimen>
|
||||
<dimen name="grid_checkbox_margin">6dp</dimen>
|
||||
<dimen name="grid_thumbnail_margin_bottom">18dp</dimen>
|
||||
<dimen name="grid_bottom_view_margin_bottom">10dp</dimen>
|
||||
<dimen name="grid_bottom_view_margin_end">6dp</dimen>
|
||||
<dimen name="grid_bottom_view_height">20dp</dimen>
|
||||
<dimen name="grid_container_width">130dp</dimen>
|
||||
<dimen name="grid_container_height">120dp</dimen>
|
||||
<dimen name="grid_filename_width">80dp</dimen>
|
||||
<dimen name="notification_icon_layout_right_end_margin">21dp</dimen>
|
||||
<dimen name="notification_list_item_grid_layout_left_start_margin">-8dp</dimen>
|
||||
<dimen name="uploader_list_separator_height">1dp</dimen>
|
||||
|
@ -110,12 +126,6 @@
|
|||
<dimen name="contactlist_item_icon_layout_height">40dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_width">72dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_height">72dp</dimen>
|
||||
<dimen name="grid_image_icon_margin">14dp</dimen>
|
||||
<dimen name="grid_image_icon_padding">14dp</dimen>
|
||||
<dimen name="grid_item_shared_icon_layout_top_margin">24dp</dimen>
|
||||
<dimen name="grid_item_local_file_indicator_layout_width">16dp</dimen>
|
||||
<dimen name="grid_item_local_file_indicator_layout_height">16dp</dimen>
|
||||
<dimen name="grid_sync_item_layout_next_text_size">22sp</dimen>
|
||||
<dimen name="grid_sync_item_layout_counter_text_size">22sp</dimen>
|
||||
<dimen name="list_item_favorite_action_layout_width">14dp</dimen>
|
||||
<dimen name="list_item_favorite_action_layout_height">14dp</dimen>
|
||||
|
|
|
@ -808,6 +808,7 @@
|
|||
<string name="no_map_app_availble">No App available to handle maps</string>
|
||||
<string name="share_via_link_hide_download">Hide download</string>
|
||||
<string name="unread_comments">Unread comments exist</string>
|
||||
<string name="grid_file_features_live_photo_content_description">This icon indicates availability of live photo</string>
|
||||
<string name="richdocuments_failed_to_load_document">Failed to load document!</string>
|
||||
<string name="create_new_document">Create new document</string>
|
||||
<string name="create_new_spreadsheet">Create new spreadsheet</string>
|
||||
|
@ -877,8 +878,9 @@
|
|||
<string name="etm_background_job_name">Job name</string>
|
||||
<string name="etm_background_job_user">User</string>
|
||||
<string name="etm_background_job_state">State</string>
|
||||
<string name="etm_background_job_started">Started</string>
|
||||
<string name="etm_background_job_started">Created</string>
|
||||
<string name="etm_background_job_progress">Progress</string>
|
||||
<string name="etm_background_execution_count">Times run in 48h</string>
|
||||
<string name="etm_migrations">Migrations (app upgrade)</string>
|
||||
<string name="etm_transfer">File transfer</string>
|
||||
<string name="etm_transfer_remote_path">Remote path</string>
|
||||
|
@ -914,7 +916,7 @@
|
|||
<string name="failed_to_start_editor">Failed to start editor</string>
|
||||
<string name="create_rich_workspace">Add folder info</string>
|
||||
<string name="creates_rich_workspace">creates folder info</string>
|
||||
<string name="uploader_local_files_uploaded">Try to upload local files again</string>
|
||||
<string name="uploader_local_files_uploaded">Retry to upload failed local files</string>
|
||||
<string name="uploader_file_not_found_on_server_message">We couldnt locate the file on server. Another user may have deleted the file</string>
|
||||
<string name="uploader_file_not_found_message">File not found. Are you sure that this file exists or has a previous conflict not been resolved?</string>
|
||||
<string name="uploader_upload_failed_sync_conflict_error">File upload conflict</string>
|
||||
|
|
Loading…
Reference in a new issue