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
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="com.owncloud.android.providers.PERMISSION" />
<queries>
<package android:name="com.nextcloud.talk2" />
<package android:name="com.nextcloud.client" />
<package android:name="com.nextcloud.android.beta" />
</queries>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,22 @@
import android.accounts.NetworkErrorException;
import android.animation.AnimatorInflater;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
Expand All @@ -36,6 +46,7 @@
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
Expand All @@ -44,6 +55,7 @@
import androidx.core.view.GravityCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.selection.SelectionTracker;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
Expand All @@ -66,11 +78,14 @@
import com.nextcloud.android.sso.helper.SingleAccountHelper;

import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import hct.Hct;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.owncloud.notes.LockedActivity;
import it.niedermann.owncloud.notes.NotesApplication;
Expand Down Expand Up @@ -108,6 +123,7 @@
import it.niedermann.owncloud.notes.shared.util.CustomAppGlideModule;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.ShareUtil;
import it.niedermann.owncloud.notes.util.LinkHelper;

public class MainActivity extends LockedActivity implements NoteClickListener, AccountPickerListener, AccountSwitcherListener, CategoryDialogFragment.CategoryDialogListener {

Expand Down Expand Up @@ -170,6 +186,8 @@ protected void onCreate(Bundle savedInstanceState) {

setupToolbars();
setupNavigationList();
setupDrawerAppMenu();
setupDrawerAppMenuListener();
setupNotesList();

mainViewModel.getAccountsCount().observe(this, (count) -> {
Expand Down Expand Up @@ -346,6 +364,52 @@ public void handleOnBackPressed() {
});
}

private void setupDrawerAppMenu() {
// hide ecosystem apps based user preference or for branded clients
boolean isShowEcosystemApps = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext())
.getBoolean(getString(R.string.pref_key_show_ecosystem_apps), true);
boolean shouldHideTopBanner = getResources().getBoolean(R.bool.is_branded_client) || !isShowEcosystemApps;

if (shouldHideTopBanner) {
binding.drawerEcosystemApps.setVisibility(GONE);
} else {
binding.drawerEcosystemApps.setVisibility(VISIBLE);
}
}

private void setupDrawerAppMenuListener() {
// Add listeners to the ecosystem items to launch the app or app-store
binding.drawerEcosystemFiles.setOnClickListener(v -> LinkHelper.INSTANCE.openAppOrStore(LinkHelper.APP_NEXTCLOUD_FILES, mainViewModel.getCurrentAccount().getValue().getAccountName(), this));
binding.drawerEcosystemTalk.setOnClickListener(v -> LinkHelper.INSTANCE.openAppOrStore(LinkHelper.APP_NEXTCLOUD_TALK, mainViewModel.getCurrentAccount().getValue().getAccountName(), this));
binding.drawerEcosystemMore.setOnClickListener(v -> LinkHelper.INSTANCE.openAppStore("Nextcloud", true, this));
}

private void themeDrawerAppMenu(int color) {
ColorStateList colorStateList = ColorStateList.valueOf(color);
binding.drawerEcosystemFilesIcon.setImageTintList(colorStateList);
((GradientDrawable) binding.drawerEcosystemFilesIcon.getBackground())
.setStroke(convertDpToPixel(1, this), color);
binding.drawerEcosystemFilesText.setTextColor(color);

binding.drawerEcosystemTalkIcon.setImageTintList(colorStateList);
((GradientDrawable) binding.drawerEcosystemTalkIcon.getBackground())
.setStroke(convertDpToPixel(1, this), color);
binding.drawerEcosystemTalkText.setTextColor(color);

binding.drawerEcosystemMoreIcon.setImageTintList(colorStateList);
((GradientDrawable) binding.drawerEcosystemMoreIcon.getBackground())
.setStroke(convertDpToPixel(1, this), color);
binding.drawerEcosystemMoreText.setTextColor(color);
}

public static int convertDpToPixel(float dp, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();

return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
}

private void showAppAccountNotFoundAlertDialog(NextcloudFilesAppAccountNotFoundException e) {
final MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(this)
.setTitle(NextcloudFilesAppAccountNotFoundException.class.getSimpleName())
Expand Down Expand Up @@ -632,6 +696,7 @@ public void applyBrand(int color) {
@ColorInt final int headerTextColor = ColorUtil.getForegroundColorForBackgroundColor(color);
binding.appName.setTextColor(headerTextColor);
DrawableCompat.setTint(binding.logo.getDrawable(), headerTextColor);
themeDrawerAppMenu(headerTextColor);

adapter.applyBrand(color);
adapterCategories.applyBrand(color);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra
private BrandedSwitchPreference backgroundSyncPref;
private BrandedSwitchPreference keepScreenOnPref;
private BrandedSwitchPreference enableDirectEditorPref;
private BrandedSwitchPreference showEcosystemAppBarPref;

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
Expand All @@ -50,6 +51,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

fontPref = findPreference(getString(R.string.pref_key_font));

showEcosystemAppBarPref = findPreference(getString(R.string.pref_key_show_ecosystem_apps));

gridViewPref = findPreference(getString(R.string.pref_key_gridview));
if (gridViewPref != null) {
gridViewPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
Expand Down Expand Up @@ -141,6 +144,7 @@ public void applyBrand(int color) {
lockPref.applyBrand(color);
wifiOnlyPref.applyBrand(color);
gridViewPref.applyBrand(color);
showEcosystemAppBarPref.applyBrand(color);
preventScreenCapturePref.applyBrand(color);
backgroundSyncPref.applyBrand(color);
keepScreenOnPref.applyBrand(color);
Expand Down
132 changes: 132 additions & 0 deletions app/src/main/java/it/niedermann/owncloud/notes/util/LinkHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Nextcloud Android Common Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: MIT
*/

package it.niedermann.owncloud.notes.util

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import com.owncloud.android.lib.common.utils.Log_OC
import java.util.Locale

/**
* Helper class for opening Nextcloud apps if present
* or falling back to opening the app store
* in case the app is not yet installed on the device.
*/
object LinkHelper {
const val APP_NEXTCLOUD_FILES = "com.nextcloud.client"
const val APP_NEXTCLOUD_NOTES = "it.niedermann.owncloud.notes"
const val APP_NEXTCLOUD_TALK = "com.nextcloud.talk2"
const val KEY_ACCOUNT: String = "KEY_ACCOUNT"
private const val TAG = "LinkHelper"

/**
* Open specified app and, if not installed redirect to corresponding download.
*
* @param packageName of app to be opened
* @param userHash to pass in intent
*/
fun openAppOrStore(
packageName: String,
userHash: String?,
context: Context,
) {
val intent = context.packageManager.getLaunchIntentForPackage(packageName)
if (intent != null) {
// app installed - open directly
// TODO handle null user?
intent.putExtra(KEY_ACCOUNT, userHash)
context.startActivity(intent)
} else {
// app not found - open market (Google Play Store, F-Droid, etc.)
openAppStore(packageName, false, context)
}
}

/**
* Open app store page of specified app or search for specified string. Will attempt to open browser when no app
* store is available.
*
* @param string packageName or url-encoded search string
* @param search false -> show app corresponding to packageName; true -> open search for string
*/
fun openAppStore(
string: String,
search: Boolean = false,
context: Context,
) {
var suffix = (if (search) "search?q=" else "details?id=") + string
val intent = Intent(Intent.ACTION_VIEW, "market://$suffix".toUri())
try {
context.startActivity(intent)
} catch (activityNotFoundException1: ActivityNotFoundException) {
// all is lost: open google play store web page for app
if (!search) {
suffix = "apps/$suffix"
}
intent.setData("https://play.google.com/store/$suffix".toUri())
context.startActivity(intent)
}
}

// region Validation
private const val HTTP = "http"
private const val HTTPS = "https"
private const val FILE = "file"
private const val CONTENT = "content"

/**
* Validates if a string can be converted to a valid URI
*/
@Suppress("TooGenericExceptionCaught", "ReturnCount")
fun validateAndGetURI(uriString: String?): Uri? {
if (uriString.isNullOrBlank()) {
Log_OC.w(TAG, "Given uriString is null or blank")
return null
}

return try {
val uri = uriString.toUri()
if (uri.scheme == null) {
return null
}

val validSchemes = listOf(HTTP, HTTPS, FILE, CONTENT)
if (uri.scheme in validSchemes) uri else null
} catch (e: Exception) {
Log_OC.e(TAG, "Invalid URI string: $uriString -- $e")
null
}
}

/**
* Validates if a URL string is valid
*/
@Suppress("TooGenericExceptionCaught", "ReturnCount")
fun validateAndGetURL(url: String?): String? {
if (url.isNullOrBlank()) {
Log_OC.w(TAG, "Given url is null or blank")
return null
}

return try {
val uri = url.toUri()
if (uri.scheme == null) {
return null
}
val validSchemes = listOf(HTTP, HTTPS)
if (uri.scheme in validSchemes) url else null
} catch (e: Exception) {
Log_OC.e(TAG, "Invalid URL: $url -- $e")
null
}
}
// endregion
}
16 changes: 16 additions & 0 deletions app/src/main/res/drawable/ic_assistant.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="#757575"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,9l1.25,-2.75L23,5l-2.75,-1.25L19,1l-1.25,2.75L15,5l2.75,1.25L19,9zM11.5,9.5L9,4 6.5,9.5 1,12l5.5,2.5L9,20l2.5,-5.5L17,12l-5.5,-2.5zM19,15l-1.25,2.75L15,19l2.75,1.25L19,23l1.25,-2.75L23,19l-2.75,-1.25L19,15z" />
</vector>
29 changes: 29 additions & 0 deletions app/src/main/res/drawable/ic_more_apps.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!--
~ Nextcloud Notes - Android Client
~
~ SPDX-FileCopyrightText: 2025 Andy Scherzinger <info@andy-scherzinger>
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:fillColor="#FF000000"
android:fillType="nonZero"
android:pathData="M17.002,13.258L17.002,11.281C17.002,10.903 17.093,10.562 17.274,10.259C17.456,9.956 17.697,9.725 17.998,9.566C18.606,9.253 19.234,9.017 19.882,8.861C20.53,8.704 21.188,8.626 21.856,8.626C22.521,8.626 23.175,8.704 23.82,8.861C24.465,9.017 25.091,9.253 25.7,9.566C26.001,9.725 26.242,9.956 26.423,10.259C26.605,10.562 26.695,10.903 26.695,11.281L26.695,13.258L17.002,13.258ZM27.871,13.258L27.871,11.115C27.871,10.727 27.765,10.303 27.553,9.843C27.341,9.382 27.009,8.99 26.557,8.667C27.138,8.722 27.684,8.826 28.196,8.978C28.707,9.13 29.18,9.326 29.613,9.566C29.999,9.785 30.293,10.029 30.497,10.297C30.7,10.566 30.802,10.838 30.802,11.115L30.802,13.258L27.871,13.258ZM21.842,7.893C21.127,7.893 20.515,7.638 20.006,7.129C19.497,6.62 19.242,6.008 19.242,5.293C19.242,4.578 19.497,3.966 20.006,3.457C20.515,2.948 21.127,2.694 21.842,2.694C22.557,2.694 23.169,2.948 23.678,3.457C24.187,3.966 24.441,4.578 24.441,5.293C24.441,6.008 24.187,6.62 23.678,7.129C23.169,7.638 22.557,7.893 21.842,7.893ZM28.548,5.293C28.548,6.008 28.294,6.62 27.785,7.129C27.276,7.638 26.664,7.893 25.949,7.893C25.81,7.893 25.621,7.877 25.382,7.844C25.142,7.812 24.953,7.773 24.815,7.727C25.082,7.377 25.283,6.995 25.416,6.581C25.55,6.168 25.617,5.738 25.617,5.293C25.617,4.848 25.55,4.419 25.416,4.005C25.283,3.592 25.082,3.21 24.815,2.86C24.999,2.795 25.188,2.751 25.382,2.728C25.575,2.705 25.764,2.694 25.949,2.694C26.664,2.694 27.276,2.948 27.785,3.457C28.294,3.966 28.548,4.578 28.548,5.293Z" />
<path
android:fillColor="#FF000000"
android:fillType="nonZero"
android:pathData="M2.726,29.614C2.253,29.614 1.849,29.446 1.513,29.11C1.176,28.773 1.008,28.369 1.008,27.897L1.008,19.989C1.008,19.517 1.176,19.113 1.513,18.776C1.849,18.44 2.253,18.272 2.726,18.272L13.291,18.272C13.763,18.272 14.167,18.44 14.504,18.776C14.84,19.113 15.008,19.517 15.008,19.989L15.008,27.897C15.008,28.369 14.84,28.773 14.504,29.11C14.167,29.446 13.763,29.614 13.291,29.614L2.726,29.614ZM8.008,24.964L13.291,21.739L13.291,19.989L8.008,23.295L2.726,19.989L2.726,21.739L8.008,24.964Z" />
<path
android:fillColor="#FF000000"
android:fillType="nonZero"
android:pathData="M18.594,25.823C18.086,25.823 17.653,25.642 17.294,25.281C16.934,24.92 16.755,24.485 16.755,23.978C16.755,23.471 16.935,23.037 17.297,22.678C17.658,22.319 18.092,22.139 18.6,22.139C19.107,22.139 19.541,22.32 19.9,22.681C20.259,23.043 20.439,23.477 20.439,23.984C20.439,24.492 20.258,24.925 19.897,25.284C19.535,25.643 19.101,25.823 18.594,25.823ZM24.002,25.823C23.494,25.823 23.061,25.642 22.702,25.281C22.343,24.92 22.163,24.485 22.163,23.978C22.163,23.471 22.344,23.037 22.705,22.678C23.066,22.319 23.501,22.139 24.008,22.139C24.515,22.139 24.949,22.32 25.308,22.681C25.667,23.043 25.847,23.477 25.847,23.984C25.847,24.492 25.666,24.925 25.305,25.284C24.943,25.643 24.509,25.823 24.002,25.823ZM29.41,25.823C28.902,25.823 28.469,25.642 28.11,25.281C27.751,24.92 27.571,24.485 27.571,23.978C27.571,23.471 27.752,23.037 28.113,22.678C28.474,22.319 28.909,22.139 29.416,22.139C29.923,22.139 30.357,22.32 30.716,22.681C31.075,23.043 31.255,23.477 31.255,23.984C31.255,24.492 31.074,24.925 30.713,25.284C30.351,25.643 29.917,25.823 29.41,25.823Z" />
<path
android:fillColor="#FF000000"
android:fillType="nonZero"
android:pathData="M3.142,14.911C2.76,14.911 2.433,14.775 2.161,14.503C1.889,14.231 1.753,13.904 1.753,13.522L1.753,3.799C1.753,3.417 1.889,3.091 2.161,2.819C2.433,2.547 2.76,2.411 3.142,2.411L3.836,2.411L3.836,1.022L5.225,1.022L5.225,2.411L10.781,2.411L10.781,1.022L12.17,1.022L12.17,2.411L12.864,2.411C13.246,2.411 13.573,2.547 13.845,2.819C14.117,3.091 14.253,3.417 14.253,3.799L14.253,13.522C14.253,13.904 14.117,14.231 13.845,14.503C13.573,14.775 13.246,14.911 12.864,14.911L3.142,14.911ZM3.142,13.522L12.864,13.522L12.864,6.577L3.142,6.577L3.142,13.522Z" />
</vector>
15 changes: 15 additions & 0 deletions app/src/main/res/drawable/ic_rocket_launch_grey600_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!--
~ 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:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF757575"
android:pathData="M226,401L304,434Q318,406 333,380Q348,354 366,328L310,317Q310,317 310,317Q310,317 310,317L226,401ZM368,484L482,597Q524,581 572,548Q620,515 662,473Q732,403 771.5,317.5Q811,232 806,160Q734,155 648,194.5Q562,234 492,304Q450,346 417,394Q384,442 368,484ZM546,419Q523,396 523,362.5Q523,329 546,306Q569,283 603,283Q637,283 660,306Q683,329 683,362.5Q683,396 660,419Q637,442 603,442Q569,442 546,419ZM565,740L649,656Q649,656 649,656Q649,656 649,656L638,600Q612,618 586,632.5Q560,647 532,661L565,740ZM878,87Q897,208 854.5,322.5Q812,437 708,541Q708,541 708,541Q708,541 708,541L728,640Q732,660 726,679Q720,698 706,712L538,880L454,683L283,512L86,428L253,260Q267,246 286.5,240Q306,234 326,238L425,258Q425,258 425,258Q425,258 425,258Q529,154 643,111Q757,68 878,87ZM157,639Q192,604 242.5,603.5Q293,603 328,638Q363,673 362.5,723.5Q362,774 327,809Q302,834 243.5,852Q185,870 82,884Q96,781 114,722.5Q132,664 157,639ZM214,695Q204,705 194,731.5Q184,758 180,785Q207,781 233.5,771.5Q260,762 270,752Q282,740 283,723Q284,706 272,694Q260,682 243,682.5Q226,683 214,695Z" />
</vector>
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/white_outline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Notes - Android Client
~
~ SPDX-FileCopyrightText: 2025 Nextcloud Gmbh and Nextcloud contributors
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/fg_contrast" />
</shape>
Loading
Loading