Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.ColorInt;
Expand All @@ -28,7 +29,6 @@
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.DrawableCompat;

import com.google.android.material.appbar.AppBarLayout;
Expand Down Expand Up @@ -220,6 +220,28 @@ public void themeToolbarSearchView(@NonNull SearchView searchView) {
});
}

/**
* @deprecated Should be replaced with {@link com.google.android.material.search.SearchView}
*/
@Deprecated
public void themeContentSearchView(@NonNull SearchView searchView) {
withScheme(searchView, scheme -> {
// hacky as no default way is provided
final var editText = (AppCompatAutoCompleteTextView) searchView
.findViewById(androidx.appcompat.R.id.search_src_text);
final var searchPlate = (LinearLayout) searchView.findViewById(androidx.appcompat.R.id.search_plate);
final var closeButton = (ImageView) searchView.findViewById(androidx.appcompat.R.id.search_close_btn);
final var searchButton = (ImageView) searchView.findViewById(androidx.appcompat.R.id.search_button);
editText.setHintTextColor(dynamicColor.onSurfaceVariant().getArgb(scheme));
editText.setHighlightColor(dynamicColor.inverseOnSurface().getArgb(scheme));
editText.setTextColor(dynamicColor.onSurface().getArgb(scheme));
closeButton.setColorFilter(dynamicColor.onSurface().getArgb(scheme));
searchButton.setColorFilter(dynamicColor.onSurface().getArgb(scheme));
searchPlate.setBackgroundColor(dynamicColor.surfaceContainerHigh().getArgb(scheme));
return searchView;
});
}

public void themeInternalLinkIcon(ImageView view) {
withScheme(view, scheme -> {
view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@
NotesListWidgetData.class,
ShareEntity.class,
Capabilities.class
}, version = 27,
}, version = 28,
autoMigrations = {
@AutoMigration(from = 25, to = 26),
@AutoMigration(from = 26, to = 27),
@AutoMigration(from = 27, to = 28),
}
)
@TypeConverters({Converters.class})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ data class ShareEntity(
val displayname_file_owner: String? = null,
val uid_owner: String? = null,
val displayname_owner: String? = null,
val url: String? = null
val url: String? = null,
val expiration_date: Long? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@

import org.jetbrains.annotations.NotNull;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -466,6 +468,22 @@ public void showSharingMenuActionSheet(OCShare share) {
}
}

@Override
public void showShareExpirationSnackbar(OCShare share) {
String expirationDescription = getString(
R.string.share_expires,
SimpleDateFormat.getDateInstance().format(new Date(share.getExpirationDate()))
);
Snackbar snackbar = Snackbar
.make(binding.getRoot(), expirationDescription, Snackbar.LENGTH_LONG)
.setAction(R.string.dismiss, view -> {
// Clicking on empty action will dismiss snackbar
});
final var util = BrandingUtil.of(BrandingUtil.readBrandMainColor(this), this);
util.material.themeSnackbar(snackbar);
snackbar.show();
}

@Override
public void showPermissionsDialog(OCShare share) {
new QuickSharingPermissionsBottomSheetDialog(this, this, share).show();
Expand Down Expand Up @@ -807,12 +825,17 @@ public void applyBrand(int color) {
final var util = BrandingUtil.of(color, this);
util.platform.themeStatusBar(this);
util.material.themeToolbar(binding.toolbar);
util.androidx.themeToolbarSearchView(binding.searchView);
util.notes.themeContentSearchView(binding.searchView);
util.platform.colorImageView(binding.searchViewIcon, ColorRole.ON_SURFACE_VARIANT);
util.platform.colorImageView(binding.pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT);
util.platform.colorCircularProgressBar(binding.loadingLayoutIndicator, ColorRole.PRIMARY);
util.platform.themeHorizontalProgressBar(binding.progressBar);
util.platform.colorViewBackground(getWindow().getDecorView());
util.platform.colorViewBackground(binding.getRoot());
util.material.colorMaterialButtonPrimaryOutlined(binding.btnShareButton);
util.platform.colorTextView(binding.title, ColorRole.ON_SURFACE);
util.platform.colorTextView(binding.fileName, ColorRole.ON_SURFACE_VARIANT);
util.notes.themeSearchCardView(binding.searchCardWrapper);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,35 @@ import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull
import androidx.cursoradapter.widget.CursorAdapter
import it.niedermann.owncloud.notes.R
import it.niedermann.owncloud.notes.branding.BrandingUtil
import it.niedermann.owncloud.notes.databinding.ItemSuggestionAdapterBinding
import it.niedermann.owncloud.notes.persistence.entity.Account
import it.niedermann.owncloud.notes.share.helper.AvatarLoader

class SuggestionAdapter(context: Context, cursor: Cursor?, private val account: Account) : CursorAdapter(context, cursor, false) {
/**
* [CursorAdapter] used to display search suggestions for sharees for sharing notes.
*
* This adapter handles the layout and binding of suggestion data, including the
* display of user avatars or system icons based on the provided search cursor.
*
* @param context The [Context] in which the adapter is running.
* @param cursor The [Cursor] from which to get the data.
* @param account The [Account] used for loading authenticated avatars.
*/
class SuggestionAdapter(context: Context, cursor: Cursor?, private val account: Account) :
CursorAdapter(context, cursor, false) {
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
val inflater = LayoutInflater.from(context)
return inflater.inflate(R.layout.item_suggestion_adapter, parent, false)
val binding = ItemSuggestionAdapterBinding.inflate(LayoutInflater.from(context), parent, false)
val brandingUtil = BrandingUtil.of(BrandingUtil.readBrandMainColor(parent.context), parent.context)
binding.root.setBackgroundColor(brandingUtil.getScheme(parent.context).surfaceContainerHigh)
return binding.root
}

override fun bindView(view: View, context: Context, cursor: Cursor) {
val suggestion =
cursor.getString(cursor.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1))
view.findViewById<TextView>(R.id.suggestion_text).text = suggestion


val icon = view.findViewById<ImageView>(R.id.suggestion_icon)
val iconColumn = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
package it.niedermann.owncloud.notes.share.adapter.holder;

import android.content.Context;
import android.graphics.PorterDuff;
import android.text.TextUtils;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.core.content.res.ResourcesCompat;

import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.lib.resources.shares.ShareType;

import java.text.SimpleDateFormat;
import java.util.Date;

import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandedViewHolder;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
Expand Down Expand Up @@ -83,6 +84,21 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) {
binding.shareByLinkContainer.setOnClickListener(v -> listener.showPermissionsDialog(publicShare));
}

if (publicShare.getExpirationDate() > 0) {
String expirationDescription = context.getString(
R.string.share_expires,
SimpleDateFormat.getDateInstance().format(new Date(publicShare.getExpirationDate()))
);
binding.expirationStatus.setContentDescription(expirationDescription);
binding.expirationStatus.setVisibility(View.VISIBLE);
binding.shareIconContainer.setOnClickListener(
v -> listener.showShareExpirationSnackbar(publicShare)
);
} else {
binding.expirationStatus.setContentDescription(null);
binding.expirationStatus.setVisibility(View.GONE);
}

binding.copyLink.setOnClickListener(v -> listener.copyLink(publicShare));
}

Expand All @@ -102,6 +118,7 @@ public void applyBrand(int color) {
brandingUtil.androidx.colorPrimaryTextViewElement(binding.permissionName);
brandingUtil.platform.colorTextView(binding.label, ColorRole.ON_SURFACE);
brandingUtil.platform.colorImageViewBackgroundAndIcon(binding.icon);
brandingUtil.platform.colorImageView(binding.expirationStatus, ColorRole.ON_PRIMARY_CONTAINER);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface ShareeListAdapterListener {

void showSharingMenuActionSheet(OCShare share);

void showShareExpirationSnackbar(OCShare share);

void copyInternalLink();

void createPublicShareLink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,9 @@ data class CreateShareResponse(

@Expose
@SerializedName("password")
val password: String?
val password: String?,

@Expose
@SerializedName("expiration")
val expirationDate: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ package it.niedermann.owncloud.notes.share.model

import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import it.niedermann.owncloud.notes.shared.util.extensions.toExpirationDateLong

fun List<CreateShareResponse>.toOCShareList(): List<OCShare> {
return map { response ->
response.toOCShare()
}.filter { it.id != -1L }
}
/**
* Maps a list of [CreateShareResponse] to a list of [OCShare] objects.
*
* Filters out any responses that could not be parsed correctly (where the ID is -1).
*
* @return A list of valid [OCShare] instances.
*/
fun List<CreateShareResponse>.toOCShareList(): List<OCShare> = map { response ->
response.toOCShare()
}.filter { it.id != -1L }

fun CreateShareResponse.toOCShare(): OCShare {
val response = this
Expand All @@ -31,13 +37,14 @@ fun CreateShareResponse.toOCShare(): OCShare {
sharedWithDisplayName = response.shareWithDisplayname
isFolder = response.itemType == "folder"
userId = response.uidOwner
shareLink = response.url
shareLink = response.url
isPasswordProtected = !response.password.isNullOrEmpty()
note = response.note
isHideFileDownload = (response.hideDownload == 1L)
label = response.label
isHasPreview = response.hasPreview
mimetype = response.mimetype
ownerDisplayName = response.displaynameOwner
expirationDate = response.expirationDate?.toExpirationDateLong() ?: 0L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import it.niedermann.owncloud.notes.shared.model.NotesSettings
import it.niedermann.owncloud.notes.shared.model.OcsResponse
import it.niedermann.owncloud.notes.shared.util.StringConstants
import it.niedermann.owncloud.notes.shared.util.extensions.getErrorMessage
import it.niedermann.owncloud.notes.shared.util.extensions.toExpirationDateLong
import it.niedermann.owncloud.notes.shared.util.extensions.toExpirationDateString
import org.json.JSONObject
import java.util.Date
Expand Down Expand Up @@ -109,6 +110,7 @@ class ShareRepository(private val applicationContext: Context, private val accou
val uidOwner = map?.get("uid_owner") as? String
val displayNameOwner = map?.get("displayname_owner") as? String
val url = map?.get("url") as? String
val expirationDateString = map?.get("expiration") as? String

id?.toInt()?.let {
val entity = ShareEntity(
Expand All @@ -122,7 +124,8 @@ class ShareRepository(private val applicationContext: Context, private val accou
displayname_file_owner = displayNameFileOwner,
uid_owner = uidOwner,
displayname_owner = displayNameOwner,
url = url
url = url,
expiration_date = expirationDateString?.toExpirationDateLong()
)

entities.add(entity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

fun Date.toExpirationDateString(): String {
return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this)
}
fun Date.toExpirationDateString(): String = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this)

/**
* Parses an expiration date string in "yyyy-MM-dd 00:00:00" format into a millisecond timestamp
* representing the start of that day.
*
* @return The time in milliseconds since the epoch, or 0 if parsing fails.
*/
fun String.toExpirationDateLong(): Long =
SimpleDateFormat("yyyy-MM-dd 00:00:00", Locale.getDefault()).parse(this)?.time ?: 0L
16 changes: 16 additions & 0 deletions app/src/main/res/drawable/schedule_24px.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
~ Nextcloud Notes - Android Client
~
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/icon_color_default"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M612,668L668,612L520,464L520,280L440,280L440,496L612,668ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q613,800 706.5,706.5Q800,613 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,613 253.5,706.5Q347,800 480,800Z" />
</vector>
Loading
Loading