mirror of
https://github.com/nextcloud/android.git
synced 2024-11-23 21:55:48 +03:00
Add dashboard widgets
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me> Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com> Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
423944137c
commit
efa886b455
48 changed files with 2033 additions and 164 deletions
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
139
app/src/androidTest/java/com/nextcloud/ui/BitmapIT.kt
Normal file
139
app/src/androidTest/java/com/nextcloud/ui/BitmapIT.kt
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.ui
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||||
|
import com.nextcloud.client.TestActivity
|
||||||
|
import com.owncloud.android.AbstractIT
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.utils.BitmapUtils
|
||||||
|
import com.owncloud.android.utils.ScreenshotTest
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class BitmapIT : AbstractIT() {
|
||||||
|
@get:Rule
|
||||||
|
val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ScreenshotTest
|
||||||
|
fun roundBitmap() {
|
||||||
|
val file = getFile("christine.jpg")
|
||||||
|
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||||
|
|
||||||
|
val activity = testActivityRule.launchActivity(null)
|
||||||
|
val imageView = ImageView(activity).apply {
|
||||||
|
setImageBitmap(bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmap2 = BitmapFactory.decodeFile(file.absolutePath)
|
||||||
|
val imageView2 = ImageView(activity).apply {
|
||||||
|
setImageBitmap(BitmapUtils.roundBitmap(bitmap2))
|
||||||
|
}
|
||||||
|
|
||||||
|
val linearLayout = LinearLayout(activity).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
setBackgroundColor(context.getColor(R.color.grey_200))
|
||||||
|
}
|
||||||
|
linearLayout.addView(imageView, 200, 200)
|
||||||
|
linearLayout.addView(imageView2, 200, 200)
|
||||||
|
activity.addView(linearLayout)
|
||||||
|
|
||||||
|
screenshot(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// @ScreenshotTest
|
||||||
|
// fun glideSVG() {
|
||||||
|
// val activity = testActivityRule.launchActivity(null)
|
||||||
|
// val accountProvider = UserAccountManagerImpl.fromContext(activity)
|
||||||
|
// val clientFactory = ClientFactoryImpl(activity)
|
||||||
|
//
|
||||||
|
// val linearLayout = LinearLayout(activity).apply {
|
||||||
|
// orientation = LinearLayout.VERTICAL
|
||||||
|
// setBackgroundColor(context.getColor(R.color.grey_200))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val file = getFile("christine.jpg")
|
||||||
|
// val bitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||||
|
//
|
||||||
|
// ImageView(activity).apply {
|
||||||
|
// setImageBitmap(bitmap)
|
||||||
|
// linearLayout.addView(this, 50, 50)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// downloadIcon(
|
||||||
|
// client.baseUri.toString() + "/apps/files/img/app.svg",
|
||||||
|
// activity,
|
||||||
|
// linearLayout,
|
||||||
|
// accountProvider,
|
||||||
|
// clientFactory
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// downloadIcon(
|
||||||
|
// client.baseUri.toString() + "/core/img/actions/group.svg",
|
||||||
|
// activity,
|
||||||
|
// linearLayout,
|
||||||
|
// accountProvider,
|
||||||
|
// clientFactory
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// activity.addView(linearLayout)
|
||||||
|
//
|
||||||
|
// longSleep()
|
||||||
|
//
|
||||||
|
// screenshot(activity)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun downloadIcon(
|
||||||
|
// url: String,
|
||||||
|
// activity: TestActivity,
|
||||||
|
// linearLayout: LinearLayout,
|
||||||
|
// accountProvider: UserAccountManager,
|
||||||
|
// clientFactory: ClientFactory
|
||||||
|
// ) {
|
||||||
|
// val view = ImageView(activity).apply {
|
||||||
|
// linearLayout.addView(this, 50, 50)
|
||||||
|
// }
|
||||||
|
// val target = object : SimpleTarget<Drawable>() {
|
||||||
|
// override fun onResourceReady(resource: Drawable?, glideAnimation: GlideAnimation<in Drawable>?) {
|
||||||
|
// view.setColorFilter(targetContext.getColor(R.color.dark), PorterDuff.Mode.SRC_ATOP)
|
||||||
|
// view.setImageDrawable(resource)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// testActivityRule.runOnUiThread {
|
||||||
|
// DisplayUtils.downloadIcon(
|
||||||
|
// accountProvider,
|
||||||
|
// clientFactory,
|
||||||
|
// activity,
|
||||||
|
// url,
|
||||||
|
// target,
|
||||||
|
// R.drawable.ic_user
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
|
@ -40,9 +40,7 @@
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
|
@ -151,6 +149,13 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.SyncedFoldersActivity"
|
android:name=".ui.activity.SyncedFoldersActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name="com.nextcloud.client.widget.DashboardWidgetConfigurationActivity"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.nextcloud.client.jobs.MediaFoldersDetectionWork$NotificationReceiver"
|
android:name="com.nextcloud.client.jobs.MediaFoldersDetectionWork$NotificationReceiver"
|
||||||
|
@ -158,6 +163,17 @@
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.nextcloud.client.jobs.NotificationWork$NotificationReceiver"
|
android:name="com.nextcloud.client.jobs.NotificationWork$NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<receiver
|
||||||
|
android:name="com.nextcloud.client.widget.DashboardWidgetProvider"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/dashboard_widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.UploadFilesActivity"
|
android:name=".ui.activity.UploadFilesActivity"
|
||||||
|
@ -220,7 +236,6 @@
|
||||||
android:name="android.accounts.AccountAuthenticator"
|
android:name="android.accounts.AccountAuthenticator"
|
||||||
android:resource="@xml/authenticator" />
|
android:resource="@xml/authenticator" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".syncadapter.FileSyncService"
|
android:name=".syncadapter.FileSyncService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -233,6 +248,10 @@
|
||||||
android:name="android.content.SyncAdapter"
|
android:name="android.content.SyncAdapter"
|
||||||
android:resource="@xml/syncadapter_files" />
|
android:resource="@xml/syncadapter_files" />
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="com.nextcloud.client.widget.DashboardWidgetService"
|
||||||
|
android:permission="android.permission.BIND_REMOTEVIEWS"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".providers.FileContentProvider"
|
android:name=".providers.FileContentProvider"
|
||||||
|
@ -304,16 +323,12 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/exposed_filepaths" />
|
android:resource="@xml/exposed_filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".providers.DiskLruImageCacheFileProvider"
|
android:name=".providers.DiskLruImageCacheFileProvider"
|
||||||
android:authorities="@string/image_cache_provider_authority"
|
android:authorities="@string/image_cache_provider_authority"
|
||||||
|
android:exported="true"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
android:permission="android.permission.MANAGE_DOCUMENTS"></provider> <!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
|
||||||
android:exported="true">
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
|
|
||||||
<!-- to "best before" dates in his fridge. -->
|
<!-- to "best before" dates in his fridge. -->
|
||||||
<!-- disable default provider -->
|
<!-- disable default provider -->
|
||||||
<provider
|
<provider
|
||||||
|
@ -327,8 +342,6 @@
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".authentication.AuthenticatorActivity"
|
android:name=".authentication.AuthenticatorActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
@ -341,7 +354,6 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".authentication.DeepLinkLoginActivity"
|
android:name=".authentication.DeepLinkLoginActivity"
|
||||||
android:clearTaskOnLaunch="true"
|
android:clearTaskOnLaunch="true"
|
||||||
|
@ -391,11 +403,9 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"
|
android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.nextcloud.client.logger.ui.LogsActivity"
|
android:name="com.nextcloud.client.logger.ui.LogsActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.nextcloud.client.errorhandling.ShowErrorActivity"
|
android:name="com.nextcloud.client.errorhandling.ShowErrorActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
@ -465,7 +475,6 @@
|
||||||
android:label="@string/manage_space_title"
|
android:label="@string/manage_space_title"
|
||||||
android:theme="@style/Theme.ownCloud" />
|
android:theme="@style/Theme.ownCloud" />
|
||||||
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.AccountManagerService"
|
android:name=".services.AccountManagerService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -476,12 +485,10 @@
|
||||||
android:name=".ui.activity.SsoGrantPermissionActivity"
|
android:name=".ui.activity.SsoGrantPermissionActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.ownCloud.Dialog.NoTitle" />
|
android:theme="@style/Theme.ownCloud.Dialog.NoTitle" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.nextcloud.client.etm.EtmActivity"
|
android:name="com.nextcloud.client.etm.EtmActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.ownCloud.Toolbar" />
|
android:theme="@style/Theme.ownCloud.Toolbar" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.preview.PreviewBitmapActivity"
|
android:name=".ui.preview.PreviewBitmapActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
|
@ -29,6 +29,9 @@ import com.nextcloud.client.media.PlayerService;
|
||||||
import com.nextcloud.client.migrations.Migrations;
|
import com.nextcloud.client.migrations.Migrations;
|
||||||
import com.nextcloud.client.onboarding.FirstRunActivity;
|
import com.nextcloud.client.onboarding.FirstRunActivity;
|
||||||
import com.nextcloud.client.onboarding.WhatsNewActivity;
|
import com.nextcloud.client.onboarding.WhatsNewActivity;
|
||||||
|
import com.nextcloud.client.widget.DashboardWidgetConfigurationActivity;
|
||||||
|
import com.nextcloud.client.widget.DashboardWidgetProvider;
|
||||||
|
import com.nextcloud.client.widget.DashboardWidgetService;
|
||||||
import com.nextcloud.ui.ChooseAccountDialogFragment;
|
import com.nextcloud.ui.ChooseAccountDialogFragment;
|
||||||
import com.nextcloud.ui.SetStatusDialogFragment;
|
import com.nextcloud.ui.SetStatusDialogFragment;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
|
@ -102,8 +105,8 @@ import com.owncloud.android.ui.fragment.FileDetailFragment;
|
||||||
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
||||||
import com.owncloud.android.ui.fragment.GalleryFragment;
|
import com.owncloud.android.ui.fragment.GalleryFragment;
|
||||||
import com.owncloud.android.ui.fragment.LocalFileListFragment;
|
import com.owncloud.android.ui.fragment.LocalFileListFragment;
|
||||||
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialogFragment;
|
|
||||||
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog;
|
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog;
|
||||||
|
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialogFragment;
|
||||||
import com.owncloud.android.ui.fragment.OCFileListFragment;
|
import com.owncloud.android.ui.fragment.OCFileListFragment;
|
||||||
import com.owncloud.android.ui.fragment.SharedListFragment;
|
import com.owncloud.android.ui.fragment.SharedListFragment;
|
||||||
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
|
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
|
||||||
|
@ -341,6 +344,9 @@ abstract class ComponentsModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract FileSyncService fileSyncService();
|
abstract FileSyncService fileSyncService();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract DashboardWidgetService dashboardWidgetService();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract PreviewPdfFragment previewPDFFragment();
|
abstract PreviewPdfFragment previewPDFFragment();
|
||||||
|
|
||||||
|
@ -430,4 +436,10 @@ abstract class ComponentsModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SyncFileNotEnoughSpaceDialogFragment syncFileNotEnoughSpaceDialogFragment();
|
abstract SyncFileNotEnoughSpaceDialogFragment syncFileNotEnoughSpaceDialogFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract DashboardWidgetConfigurationActivity dashboardWidgetConfigurationActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract DashboardWidgetProvider dashboardWidgetProvider();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,11 @@ import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
class ClientFactoryImpl implements ClientFactory {
|
public class ClientFactoryImpl implements ClientFactory {
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
ClientFactoryImpl(Context context) {
|
public ClientFactoryImpl(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ class ClientFactoryImpl implements ClientFactory {
|
||||||
public OwnCloudClient create(User user) throws CreationException {
|
public OwnCloudClient create(User user) throws CreationException {
|
||||||
try {
|
try {
|
||||||
return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context);
|
return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context);
|
||||||
} catch (OperationCanceledException|
|
} catch (OperationCanceledException |
|
||||||
AuthenticatorException|
|
AuthenticatorException |
|
||||||
IOException e) {
|
IOException e) {
|
||||||
throw new CreationException(e);
|
throw new CreationException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.nextcloud.client.account.UserAccountManagerImpl;
|
import com.nextcloud.client.account.UserAccountManagerImpl;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
|
@ -45,15 +45,14 @@ import static com.owncloud.android.ui.fragment.OCFileListFragment.FOLDER_LAYOUT_
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of application-wide preferences using {@link SharedPreferences}.
|
* Implementation of application-wide preferences using {@link SharedPreferences}.
|
||||||
*
|
* <p>
|
||||||
* Users should not use this class directly. Please use {@link AppPreferences} interface
|
* Users should not use this class directly. Please use {@link AppPreferences} interface instead.
|
||||||
* instead.
|
|
||||||
*/
|
*/
|
||||||
public final class AppPreferencesImpl implements AppPreferences {
|
public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant to access value of last path selected by the user to upload a file shared from other app.
|
* Constant to access value of last path selected by the user to upload a file shared from other app. Value handled
|
||||||
* Value handled by the app without direct access in the UI.
|
* by the app without direct access in the UI.
|
||||||
*/
|
*/
|
||||||
public static final String AUTO_PREF__LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
|
public static final String AUTO_PREF__LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
|
||||||
public static final String STORAGE_PATH = "storage_path";
|
public static final String STORAGE_PATH = "storage_path";
|
||||||
|
@ -101,7 +100,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
private final CurrentAccountProvider currentAccountProvider;
|
private final UserAccountManager userAccountManager;
|
||||||
private final ListenerRegistry listeners;
|
private final ListenerRegistry listeners;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +122,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(@Nullable final Listener listener) {
|
void remove(@Nullable final Listener listener) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
@ -133,7 +132,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (PREF__DARK_THEME.equals(key)) {
|
if (PREF__DARK_THEME.equals(key)) {
|
||||||
DarkMode mode = preferences.getDarkThemeMode();
|
DarkMode mode = preferences.getDarkThemeMode();
|
||||||
for(Listener l : listeners) {
|
for (Listener l : listeners) {
|
||||||
l.onDarkThemeModeChanged(mode);
|
l.onDarkThemeModeChanged(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,9 +140,9 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a temporary workaround to access app preferences in places that cannot use
|
* This is a temporary workaround to access app preferences in places that cannot use dependency injection yet. Use
|
||||||
* dependency injection yet. Use injected component via {@link AppPreferences} interface.
|
* injected component via {@link AppPreferences} interface.
|
||||||
*
|
* <p>
|
||||||
* WARNING: this creates new instance! it does not return app-wide singleton
|
* WARNING: this creates new instance! it does not return app-wide singleton
|
||||||
*
|
*
|
||||||
* @param context Context used to create shared preferences
|
* @param context Context used to create shared preferences
|
||||||
|
@ -151,15 +150,15 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static AppPreferences fromContext(Context context) {
|
public static AppPreferences fromContext(Context context) {
|
||||||
final CurrentAccountProvider currentAccountProvider = UserAccountManagerImpl.fromContext(context);
|
final UserAccountManager userAccountManager = UserAccountManagerImpl.fromContext(context);
|
||||||
final SharedPreferences prefs = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
|
final SharedPreferences prefs = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
return new AppPreferencesImpl(context, prefs, currentAccountProvider);
|
return new AppPreferencesImpl(context, prefs, userAccountManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPreferencesImpl(Context appContext, SharedPreferences preferences, CurrentAccountProvider currentAccountProvider) {
|
AppPreferencesImpl(Context appContext, SharedPreferences preferences, UserAccountManager userAccountManager) {
|
||||||
this.context = appContext;
|
this.context = appContext;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.currentAccountProvider = currentAccountProvider;
|
this.userAccountManager = userAccountManager;
|
||||||
this.listeners = new ListenerRegistry(this);
|
this.listeners = new ListenerRegistry(this);
|
||||||
this.preferences.registerOnSharedPreferenceChangeListener(listeners);
|
this.preferences.registerOnSharedPreferenceChangeListener(listeners);
|
||||||
}
|
}
|
||||||
|
@ -277,7 +276,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getPassCode() {
|
public String[] getPassCode() {
|
||||||
return new String[] {
|
return new String[]{
|
||||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D1, null),
|
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D1, null),
|
||||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D2, null),
|
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D2, null),
|
||||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D3, null),
|
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D3, null),
|
||||||
|
@ -293,7 +292,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
@Override
|
@Override
|
||||||
public String getFolderLayout(OCFile folder) {
|
public String getFolderLayout(OCFile folder) {
|
||||||
return getFolderPreference(context,
|
return getFolderPreference(context,
|
||||||
currentAccountProvider.getUser(),
|
userAccountManager.getUser(),
|
||||||
PREF__FOLDER_LAYOUT,
|
PREF__FOLDER_LAYOUT,
|
||||||
folder,
|
folder,
|
||||||
FOLDER_LAYOUT_LIST);
|
FOLDER_LAYOUT_LIST);
|
||||||
|
@ -302,7 +301,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
@Override
|
@Override
|
||||||
public void setFolderLayout(@Nullable OCFile folder, String layoutName) {
|
public void setFolderLayout(@Nullable OCFile folder, String layoutName) {
|
||||||
setFolderPreference(context,
|
setFolderPreference(context,
|
||||||
currentAccountProvider.getUser(),
|
userAccountManager.getUser(),
|
||||||
PREF__FOLDER_LAYOUT,
|
PREF__FOLDER_LAYOUT,
|
||||||
folder,
|
folder,
|
||||||
layoutName);
|
layoutName);
|
||||||
|
@ -311,7 +310,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
@Override
|
@Override
|
||||||
public FileSortOrder getSortOrderByFolder(OCFile folder) {
|
public FileSortOrder getSortOrderByFolder(OCFile folder) {
|
||||||
return FileSortOrder.sortOrders.get(getFolderPreference(context,
|
return FileSortOrder.sortOrders.get(getFolderPreference(context,
|
||||||
currentAccountProvider.getUser(),
|
userAccountManager.getUser(),
|
||||||
PREF__FOLDER_SORT_ORDER,
|
PREF__FOLDER_SORT_ORDER,
|
||||||
folder,
|
folder,
|
||||||
FileSortOrder.sort_a_to_z.name));
|
FileSortOrder.sort_a_to_z.name));
|
||||||
|
@ -320,7 +319,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
@Override
|
@Override
|
||||||
public void setSortOrder(@Nullable OCFile folder, FileSortOrder sortOrder) {
|
public void setSortOrder(@Nullable OCFile folder, FileSortOrder sortOrder) {
|
||||||
setFolderPreference(context,
|
setFolderPreference(context,
|
||||||
currentAccountProvider.getUser(),
|
userAccountManager.getUser(),
|
||||||
PREF__FOLDER_SORT_ORDER,
|
PREF__FOLDER_SORT_ORDER,
|
||||||
folder,
|
folder,
|
||||||
sortOrder.name);
|
sortOrder.name);
|
||||||
|
@ -333,7 +332,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileSortOrder getSortOrderByType(FileSortOrder.Type type, FileSortOrder defaultOrder) {
|
public FileSortOrder getSortOrderByType(FileSortOrder.Type type, FileSortOrder defaultOrder) {
|
||||||
User user = currentAccountProvider.getUser();
|
User user = userAccountManager.getUser();
|
||||||
if (user.isAnonymous()) {
|
if (user.isAnonymous()) {
|
||||||
return defaultOrder;
|
return defaultOrder;
|
||||||
}
|
}
|
||||||
|
@ -347,7 +346,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSortOrder(FileSortOrder.Type type, FileSortOrder sortOrder) {
|
public void setSortOrder(FileSortOrder.Type type, FileSortOrder sortOrder) {
|
||||||
User user = currentAccountProvider.getUser();
|
User user = userAccountManager.getUser();
|
||||||
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
|
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
|
||||||
dataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREF__FOLDER_SORT_ORDER + "_" + type, sortOrder.name);
|
dataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREF__FOLDER_SORT_ORDER + "_" + type, sortOrder.name);
|
||||||
}
|
}
|
||||||
|
@ -506,19 +505,19 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
@Override
|
@Override
|
||||||
public void removeLegacyPreferences() {
|
public void removeLegacyPreferences() {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.remove("instant_uploading")
|
.remove("instant_uploading")
|
||||||
.remove("instant_video_uploading")
|
.remove("instant_video_uploading")
|
||||||
.remove("instant_upload_path")
|
.remove("instant_upload_path")
|
||||||
.remove("instant_upload_path_use_subfolders")
|
.remove("instant_upload_path_use_subfolders")
|
||||||
.remove("instant_upload_on_wifi")
|
.remove("instant_upload_on_wifi")
|
||||||
.remove("instant_upload_on_charging")
|
.remove("instant_upload_on_charging")
|
||||||
.remove("instant_video_upload_path")
|
.remove("instant_video_upload_path")
|
||||||
.remove("instant_video_upload_path_use_subfolders")
|
.remove("instant_video_upload_path_use_subfolders")
|
||||||
.remove("instant_video_upload_on_wifi")
|
.remove("instant_video_upload_on_wifi")
|
||||||
.remove("instant_video_uploading")
|
.remove("instant_video_uploading")
|
||||||
.remove("instant_video_upload_on_charging")
|
.remove("instant_video_upload_on_charging")
|
||||||
.remove("prefs_instant_behaviour")
|
.remove("prefs_instant_behaviour")
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -588,13 +587,12 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get preference value for a folder.
|
* Get preference value for a folder. If folder is not set itself, it finds an ancestor that is set.
|
||||||
* If folder is not set itself, it finds an ancestor that is set.
|
|
||||||
*
|
*
|
||||||
* @param context Context object.
|
* @param context Context object.
|
||||||
* @param preferenceName Name of the preference to lookup.
|
* @param preferenceName Name of the preference to lookup.
|
||||||
* @param folder Folder.
|
* @param folder Folder.
|
||||||
* @param defaultValue Fallback value in case no ancestor is set.
|
* @param defaultValue Fallback value in case no ancestor is set.
|
||||||
* @return Preference value
|
* @return Preference value
|
||||||
*/
|
*/
|
||||||
private static String getFolderPreference(final Context context,
|
private static String getFolderPreference(final Context context,
|
||||||
|
@ -621,10 +619,10 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
/**
|
/**
|
||||||
* Set preference value for a folder.
|
* Set preference value for a folder.
|
||||||
*
|
*
|
||||||
* @param context Context object.
|
* @param context Context object.
|
||||||
* @param preferenceName Name of the preference to set.
|
* @param preferenceName Name of the preference to set.
|
||||||
* @param folder Folder.
|
* @param folder Folder.
|
||||||
* @param value Preference value to set.
|
* @param value Preference value to set.
|
||||||
*/
|
*/
|
||||||
private static void setFolderPreference(final Context context,
|
private static void setFolderPreference(final Context context,
|
||||||
final User user,
|
final User user,
|
||||||
|
@ -637,7 +635,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
||||||
|
|
||||||
private static String getKeyFromFolder(String preferenceName, @Nullable OCFile folder) {
|
private static String getKeyFromFolder(String preferenceName, @Nullable OCFile folder) {
|
||||||
final String folderIdString = String.valueOf(folder != null ? folder.getFileId() :
|
final String folderIdString = String.valueOf(folder != null ? folder.getFileId() :
|
||||||
FileDataStorageManager.ROOT_PARENT_ID);
|
FileDataStorageManager.ROOT_PARENT_ID);
|
||||||
|
|
||||||
return preferenceName + "_" + folderIdString;
|
return preferenceName + "_" + folderIdString;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package com.nextcloud.client.preferences;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public class PreferencesModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
public AppPreferences appPreferences(Context context,
|
public AppPreferences appPreferences(Context context,
|
||||||
SharedPreferences sharedPreferences,
|
SharedPreferences sharedPreferences,
|
||||||
CurrentAccountProvider currentAccountProvider) {
|
UserAccountManager userAccountManager) {
|
||||||
return new AppPreferencesImpl(context, sharedPreferences, currentAccountProvider);
|
return new AppPreferencesImpl(context, sharedPreferences, userAccountManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashBoardButtonType
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardListWidgetsRemoteOperation
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidget
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.nextcloud.client.network.ClientFactory.CreationException
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.databinding.DashboardWidgetConfigurationLayoutBinding
|
||||||
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult
|
||||||
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
|
import com.owncloud.android.ui.adapter.DashboardWidgetListAdapter
|
||||||
|
import com.owncloud.android.ui.dialog.AccountChooserInterface
|
||||||
|
import com.owncloud.android.ui.dialog.MultipleAccountsDialog
|
||||||
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DashboardWidgetConfigurationActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DashboardWidgetConfigurationInterface,
|
||||||
|
Injectable,
|
||||||
|
AccountChooserInterface {
|
||||||
|
private lateinit var mAdapter: DashboardWidgetListAdapter
|
||||||
|
private lateinit var binding: DashboardWidgetConfigurationLayoutBinding
|
||||||
|
private lateinit var currentUser: User
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var themeDrawableUtils: ThemeDrawableUtils
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var accountManager: UserAccountManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var clientFactory: ClientFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var widgetRepository: WidgetRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var widgetUpdater: DashboardWidgetUpdater
|
||||||
|
|
||||||
|
var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
|
||||||
|
public override fun onCreate(bundle: Bundle?) {
|
||||||
|
super.onCreate(bundle)
|
||||||
|
|
||||||
|
// Set the result to CANCELED. This will cause the widget host to cancel
|
||||||
|
// out of the widget placement if the user presses the back button.
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
|
||||||
|
binding = DashboardWidgetConfigurationLayoutBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
themeDrawableUtils.tintDrawable(binding.icon.drawable, getColor(R.color.dark))
|
||||||
|
|
||||||
|
val layoutManager = LinearLayoutManager(this)
|
||||||
|
// TODO follow our new architecture
|
||||||
|
mAdapter = DashboardWidgetListAdapter(themeDrawableUtils, accountManager, clientFactory, this, this)
|
||||||
|
binding.list.apply {
|
||||||
|
setHasFooter(false)
|
||||||
|
setAdapter(mAdapter)
|
||||||
|
setLayoutManager(layoutManager)
|
||||||
|
setEmptyView(binding.emptyView.emptyListView)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser = accountManager.user
|
||||||
|
if (accountManager.allUsers.size > 1) {
|
||||||
|
binding.chooseWidget.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.accountName.apply {
|
||||||
|
setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
themeDrawableUtils.tintDrawable(
|
||||||
|
AppCompatResources.getDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_baseline_arrow_drop_down_24
|
||||||
|
),
|
||||||
|
R.color.black
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = currentUser.accountName
|
||||||
|
setOnClickListener {
|
||||||
|
val dialog = MultipleAccountsDialog()
|
||||||
|
dialog.highlightCurrentlyActiveAccount = false
|
||||||
|
dialog.show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadWidgets(currentUser)
|
||||||
|
|
||||||
|
binding.close.setOnClickListener { finish() }
|
||||||
|
|
||||||
|
// Find the widget id from the intent.
|
||||||
|
appWidgetId = intent?.extras?.getInt(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
|
||||||
|
// If this activity was started with an intent without an app widget ID, finish with an error.
|
||||||
|
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadWidgets(user: User) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.emptyView.root.visibility = View.GONE
|
||||||
|
if (accountManager.allUsers.size > 1) {
|
||||||
|
binding.accountName.text = user.accountName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val client = clientFactory.createNextcloudClient(user)
|
||||||
|
val result = DashboardListWidgetsRemoteOperation().execute(client)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (result.code == RemoteOperationResult.ResultCode.FILE_NOT_FOUND) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
mAdapter.setWidgetList(null)
|
||||||
|
binding.emptyView.root.visibility = View.VISIBLE
|
||||||
|
binding.emptyView.emptyListViewHeadline.setText(R.string.widgets_not_available_title)
|
||||||
|
|
||||||
|
binding.emptyView.emptyListIcon.apply {
|
||||||
|
setImageResource(R.drawable.ic_list_empty_error)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
binding.emptyView.emptyListViewText.apply {
|
||||||
|
setText(
|
||||||
|
String.format(
|
||||||
|
getString(R.string.widgets_not_available),
|
||||||
|
getString(R.string.app_name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mAdapter.setWidgetList(result.resultData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: CreationException) {
|
||||||
|
Log_OC.e(this, "Error loading widgets for user $user", e)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
mAdapter.setWidgetList(null)
|
||||||
|
binding.emptyView.root.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
binding.emptyView.emptyListIcon.apply {
|
||||||
|
setImageResource(R.drawable.ic_list_empty_error)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
binding.emptyView.emptyListViewText.apply {
|
||||||
|
setText(R.string.common_error)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
binding.emptyView.emptyListViewAction.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
setText(R.string.reload)
|
||||||
|
setOnClickListener {
|
||||||
|
loadWidgets(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(dashboardWidget: DashboardWidget) {
|
||||||
|
widgetRepository.saveWidget(appWidgetId, dashboardWidget, currentUser)
|
||||||
|
|
||||||
|
// update widget
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||||
|
|
||||||
|
widgetUpdater.updateAppWidget(
|
||||||
|
appWidgetManager,
|
||||||
|
appWidgetId,
|
||||||
|
dashboardWidget.title,
|
||||||
|
dashboardWidget.iconUrl,
|
||||||
|
dashboardWidget.buttons?.find { it.type == DashBoardButtonType.NEW }
|
||||||
|
)
|
||||||
|
|
||||||
|
val resultValue = Intent().apply {
|
||||||
|
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(RESULT_OK, resultValue)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAccountChosen(user: User) {
|
||||||
|
currentUser = user
|
||||||
|
loadWidgets(user)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidget
|
||||||
|
|
||||||
|
interface DashboardWidgetConfigurationInterface {
|
||||||
|
fun onItemClicked(dashboardWidget: DashboardWidget)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import dagger.android.AndroidInjection
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages widgets
|
||||||
|
*/
|
||||||
|
class DashboardWidgetProvider : AppWidgetProvider() {
|
||||||
|
@Inject
|
||||||
|
lateinit var widgetRepository: WidgetRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var widgetUpdater: DashboardWidgetUpdater
|
||||||
|
|
||||||
|
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||||
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
for (appWidgetId in appWidgetIds) {
|
||||||
|
val widgetConfiguration = widgetRepository.getWidget(appWidgetId)
|
||||||
|
|
||||||
|
widgetUpdater.updateAppWidget(
|
||||||
|
appWidgetManager,
|
||||||
|
appWidgetId,
|
||||||
|
widgetConfiguration.title,
|
||||||
|
widgetConfiguration.iconUrl,
|
||||||
|
widgetConfiguration.addButton
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
super.onReceive(context, intent)
|
||||||
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
if (intent?.action == OPEN_INTENT) {
|
||||||
|
val clickIntent = Intent(Intent.ACTION_VIEW, intent.data)
|
||||||
|
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context?.startActivity(clickIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context?, appWidgetIds: IntArray) {
|
||||||
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
for (appWidgetId in appWidgetIds) {
|
||||||
|
widgetRepository.deleteWidget(appWidgetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val OPEN_INTENT = "open"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import android.widget.RemoteViewsService
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.model.StreamEncoder
|
||||||
|
import com.bumptech.glide.load.resource.file.FileToStreamDecoder
|
||||||
|
import com.bumptech.glide.request.FutureTarget
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardGetWidgetItemsRemoteOperation
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidgetItem
|
||||||
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
|
import com.owncloud.android.utils.BitmapUtils
|
||||||
|
import com.owncloud.android.utils.DisplayUtils.SVG_SIZE
|
||||||
|
import com.owncloud.android.utils.glide.CustomGlideStreamLoader
|
||||||
|
import com.owncloud.android.utils.glide.CustomGlideUriLoader
|
||||||
|
import com.owncloud.android.utils.svg.SVGorImage
|
||||||
|
import com.owncloud.android.utils.svg.SvgOrImageBitmapTranscoder
|
||||||
|
import com.owncloud.android.utils.svg.SvgOrImageDecoder
|
||||||
|
import dagger.android.AndroidInjection
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.InputStream
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DashboardWidgetService : RemoteViewsService() {
|
||||||
|
@Inject
|
||||||
|
lateinit var userAccountManager: UserAccountManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var clientFactory: ClientFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var widgetRepository: WidgetRepository
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
AndroidInjection.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||||
|
return StackRemoteViewsFactory(
|
||||||
|
this.applicationContext,
|
||||||
|
userAccountManager,
|
||||||
|
clientFactory,
|
||||||
|
intent,
|
||||||
|
widgetRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StackRemoteViewsFactory(
|
||||||
|
private val context: Context,
|
||||||
|
val userAccountManager: UserAccountManager,
|
||||||
|
val clientFactory: ClientFactory,
|
||||||
|
val intent: Intent,
|
||||||
|
val widgetRepository: WidgetRepository
|
||||||
|
) : RemoteViewsService.RemoteViewsFactory {
|
||||||
|
|
||||||
|
private lateinit var widgetConfiguration: WidgetConfiguration
|
||||||
|
private var widgetItems: List<DashboardWidgetItem> = emptyList()
|
||||||
|
private var hasLoadMore = false
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
Log_OC.d(this, "onCreate")
|
||||||
|
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
|
||||||
|
widgetConfiguration = widgetRepository.getWidget(appWidgetId)
|
||||||
|
|
||||||
|
if (!widgetConfiguration.user.isPresent) {
|
||||||
|
// TODO show error
|
||||||
|
Log_OC.e(this, "No user found!")
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDataSetChanged() {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
val client = clientFactory.createNextcloudClient(widgetConfiguration.user.get())
|
||||||
|
val result =
|
||||||
|
DashboardGetWidgetItemsRemoteOperation(widgetConfiguration.widgetId, LIMIT_SIZE).execute(client)
|
||||||
|
widgetItems = result.resultData[widgetConfiguration.widgetId] ?: emptyList()
|
||||||
|
|
||||||
|
hasLoadMore = widgetConfiguration.moreButton != null &&
|
||||||
|
widgetItems.size == LIMIT_SIZE
|
||||||
|
} catch (e: ClientFactory.CreationException) {
|
||||||
|
Log_OC.e(this, "Error updating widget", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_OC.d("WidgetService", "onDataSetChanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log_OC.d("WidgetService", "onDestroy")
|
||||||
|
|
||||||
|
widgetItems = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return if (hasLoadMore && widgetItems.isNotEmpty()) {
|
||||||
|
widgetItems.size + 1
|
||||||
|
} else {
|
||||||
|
widgetItems.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewAt(position: Int): RemoteViews {
|
||||||
|
return if (position == widgetItems.size) {
|
||||||
|
createLoadMoreView()
|
||||||
|
} else {
|
||||||
|
createItemView(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLoadMoreView(): RemoteViews {
|
||||||
|
return RemoteViews(context.packageName, R.layout.widget_item_load_more).apply {
|
||||||
|
val clickIntent = Intent(Intent.ACTION_VIEW, Uri.parse(widgetConfiguration.moreButton?.link))
|
||||||
|
setTextViewText(R.id.load_more, widgetConfiguration.moreButton?.text)
|
||||||
|
setOnClickFillInIntent(R.id.load_more_container, clickIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we will switch soon to coil and then streamline all of this
|
||||||
|
// Kotlin cannot catch multiple exception types at same time
|
||||||
|
@Suppress("NestedBlockDepth", "TooGenericExceptionCaught")
|
||||||
|
private fun createItemView(position: Int): RemoteViews {
|
||||||
|
return RemoteViews(context.packageName, R.layout.widget_item).apply {
|
||||||
|
val widgetItem = widgetItems[position]
|
||||||
|
|
||||||
|
// icon bitmap/svg
|
||||||
|
if (widgetItem.iconUrl.isNotEmpty()) {
|
||||||
|
val glide: FutureTarget<Bitmap>
|
||||||
|
if (Uri.parse(widgetItem.iconUrl).encodedPath!!.endsWith(".svg")) {
|
||||||
|
glide = Glide.with(context)
|
||||||
|
.using(
|
||||||
|
CustomGlideUriLoader(userAccountManager.user, clientFactory),
|
||||||
|
InputStream::class.java
|
||||||
|
)
|
||||||
|
.from(Uri::class.java)
|
||||||
|
.`as`(SVGorImage::class.java)
|
||||||
|
.transcode(SvgOrImageBitmapTranscoder(SVG_SIZE, SVG_SIZE), Bitmap::class.java)
|
||||||
|
.sourceEncoder(StreamEncoder())
|
||||||
|
.cacheDecoder(FileToStreamDecoder(SvgOrImageDecoder()))
|
||||||
|
.decoder(SvgOrImageDecoder())
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
||||||
|
.load(Uri.parse(widgetItem.iconUrl))
|
||||||
|
.into(SVG_SIZE, SVG_SIZE)
|
||||||
|
} else {
|
||||||
|
glide = Glide.with(context)
|
||||||
|
.using(CustomGlideStreamLoader(widgetConfiguration.user.get(), clientFactory))
|
||||||
|
.load(widgetItem.iconUrl)
|
||||||
|
.asBitmap()
|
||||||
|
.into(SVG_SIZE, SVG_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (widgetConfiguration.roundIcon) {
|
||||||
|
setImageViewBitmap(R.id.icon, BitmapUtils.roundBitmap(glide.get()))
|
||||||
|
} else {
|
||||||
|
setImageViewBitmap(R.id.icon, glide.get())
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log_OC.d(this, "Error setting icon", e)
|
||||||
|
setImageViewResource(R.id.icon, R.drawable.ic_dashboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// text
|
||||||
|
setTextViewText(R.id.title, widgetItem.title)
|
||||||
|
|
||||||
|
if (widgetItem.subtitle.isNotEmpty()) {
|
||||||
|
setViewVisibility(R.id.subtitle, View.VISIBLE)
|
||||||
|
setTextViewText(R.id.subtitle, widgetItem.subtitle)
|
||||||
|
} else {
|
||||||
|
setViewVisibility(R.id.subtitle, View.GONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgetItem.link.isNotEmpty()) {
|
||||||
|
val clickIntent = Intent(Intent.ACTION_VIEW, Uri.parse(widgetItem.link))
|
||||||
|
setOnClickFillInIntent(R.id.text_container, clickIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLoadingView(): RemoteViews? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewTypeCount(): Int {
|
||||||
|
return if (hasLoadMore) {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return position.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasStableIds(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LIMIT_SIZE = 14
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.model.StreamEncoder
|
||||||
|
import com.bumptech.glide.load.resource.file.FileToStreamDecoder
|
||||||
|
import com.bumptech.glide.request.animation.GlideAnimation
|
||||||
|
import com.bumptech.glide.request.target.AppWidgetTarget
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardButton
|
||||||
|
import com.nextcloud.client.account.CurrentAccountProvider
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.utils.BitmapUtils
|
||||||
|
import com.owncloud.android.utils.DisplayUtils.SVG_SIZE
|
||||||
|
import com.owncloud.android.utils.glide.CustomGlideUriLoader
|
||||||
|
import com.owncloud.android.utils.svg.SVGorImage
|
||||||
|
import com.owncloud.android.utils.svg.SvgOrImageBitmapTranscoder
|
||||||
|
import com.owncloud.android.utils.svg.SvgOrImageDecoder
|
||||||
|
import java.io.InputStream
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DashboardWidgetUpdater @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val clientFactory: ClientFactory,
|
||||||
|
private val accountProvider: CurrentAccountProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun updateAppWidget(
|
||||||
|
appWidgetManager: AppWidgetManager,
|
||||||
|
appWidgetId: Int,
|
||||||
|
title: String,
|
||||||
|
iconUrl: String,
|
||||||
|
addButton: DashboardButton?
|
||||||
|
) {
|
||||||
|
val intent = Intent(context, DashboardWidgetService::class.java).apply {
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||||
|
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
}
|
||||||
|
|
||||||
|
val views = RemoteViews(context.packageName, R.layout.dashboard_widget).apply {
|
||||||
|
setRemoteAdapter(R.id.list, intent)
|
||||||
|
setEmptyView(R.id.list, R.id.empty_view)
|
||||||
|
setTextViewText(R.id.title, title)
|
||||||
|
|
||||||
|
setAddButton(addButton, appWidgetId, this)
|
||||||
|
setPendingReload(this, appWidgetId)
|
||||||
|
setPendingClick(this)
|
||||||
|
loadIcon(appWidgetId, iconUrl, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||||
|
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPendingReload(remoteViews: RemoteViews, appWidgetId: Int) {
|
||||||
|
val intentUpdate = Intent(context, DashboardWidgetProvider::class.java)
|
||||||
|
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||||
|
|
||||||
|
val idArray = intArrayOf(appWidgetId)
|
||||||
|
intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray)
|
||||||
|
|
||||||
|
remoteViews.setOnClickPendingIntent(
|
||||||
|
R.id.reload,
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
appWidgetId,
|
||||||
|
intentUpdate,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clickPI needs to me mutable, as it is re-used. PendingIntent.FLAG_IMMUTABLE requires S (API 31)
|
||||||
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
|
private fun setPendingClick(remoteViews: RemoteViews) {
|
||||||
|
val clickPI = PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
|
remoteViews.setPendingIntentTemplate(R.id.list, clickPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAddButton(addButton: DashboardButton?, appWidgetId: Int, remoteViews: RemoteViews) {
|
||||||
|
// create add button
|
||||||
|
if (addButton == null) {
|
||||||
|
remoteViews.setViewVisibility(R.id.create, View.GONE)
|
||||||
|
} else {
|
||||||
|
remoteViews.setViewVisibility(R.id.create, View.VISIBLE)
|
||||||
|
remoteViews.setContentDescription(R.id.create, addButton.text)
|
||||||
|
|
||||||
|
val clickIntent = Intent(context, DashboardWidgetProvider::class.java)
|
||||||
|
clickIntent.action = DashboardWidgetProvider.OPEN_INTENT
|
||||||
|
clickIntent.data = Uri.parse(addButton.link)
|
||||||
|
|
||||||
|
remoteViews.setOnClickPendingIntent(
|
||||||
|
R.id.create,
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
appWidgetId,
|
||||||
|
clickIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadIcon(appWidgetId: Int, iconUrl: String, remoteViews: RemoteViews) {
|
||||||
|
val iconTarget = object : AppWidgetTarget(context, remoteViews, R.id.icon, appWidgetId) {
|
||||||
|
override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
|
||||||
|
if (resource != null) {
|
||||||
|
val tintedBitmap = BitmapUtils.tintImage(resource, R.color.black)
|
||||||
|
super.onResourceReady(tintedBitmap, glideAnimation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.using(
|
||||||
|
CustomGlideUriLoader(accountProvider.user, clientFactory),
|
||||||
|
InputStream::class.java
|
||||||
|
)
|
||||||
|
.from(Uri::class.java)
|
||||||
|
.`as`(SVGorImage::class.java)
|
||||||
|
.transcode(SvgOrImageBitmapTranscoder(SVG_SIZE, SVG_SIZE), Bitmap::class.java)
|
||||||
|
.sourceEncoder(StreamEncoder())
|
||||||
|
.cacheDecoder(FileToStreamDecoder(SvgOrImageDecoder()))
|
||||||
|
.decoder(SvgOrImageDecoder())
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
||||||
|
.load(Uri.parse(iconUrl))
|
||||||
|
.into(iconTarget)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardButton
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
import com.nextcloud.java.util.Optional
|
||||||
|
|
||||||
|
data class WidgetConfiguration(
|
||||||
|
val widgetId: String,
|
||||||
|
val title: String,
|
||||||
|
val iconUrl: String,
|
||||||
|
val roundIcon: Boolean,
|
||||||
|
val user: Optional<User>,
|
||||||
|
val addButton: DashboardButton?,
|
||||||
|
val moreButton: DashboardButton?
|
||||||
|
)
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.client.widget
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashBoardButtonType
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardButton
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidget
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.java.util.Optional
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class WidgetRepository @Inject constructor(
|
||||||
|
private val userAccountManager: UserAccountManager,
|
||||||
|
val preferences: SharedPreferences
|
||||||
|
) {
|
||||||
|
fun saveWidget(widgetId: Int, widget: DashboardWidget, user: User) {
|
||||||
|
val editor: SharedPreferences.Editor = preferences
|
||||||
|
.edit()
|
||||||
|
.putString(PREF__WIDGET_ID + widgetId, widget.id)
|
||||||
|
.putString(PREF__WIDGET_TITLE + widgetId, widget.title)
|
||||||
|
.putString(PREF__WIDGET_ICON + widgetId, widget.iconUrl)
|
||||||
|
.putBoolean(PREF__WIDGET_ROUND_ICON + widgetId, widget.roundIcons)
|
||||||
|
.putString(PREF__WIDGET_USER + widgetId, user.accountName)
|
||||||
|
val buttonList = widget.buttons
|
||||||
|
if (buttonList != null && buttonList.isNotEmpty()) {
|
||||||
|
for (button in buttonList) {
|
||||||
|
if (button.type == DashBoardButtonType.NEW) {
|
||||||
|
editor
|
||||||
|
.putString(PREF__WIDGET_ADD_BUTTON_TYPE + widgetId, button.type.toString())
|
||||||
|
.putString(PREF__WIDGET_ADD_BUTTON_URL + widgetId, button.link)
|
||||||
|
.putString(PREF__WIDGET_ADD_BUTTON_TEXT + widgetId, button.text)
|
||||||
|
}
|
||||||
|
if (button.type == DashBoardButtonType.MORE) {
|
||||||
|
editor
|
||||||
|
.putString(PREF__WIDGET_MORE_BUTTON_TYPE + widgetId, button.type.toString())
|
||||||
|
.putString(PREF__WIDGET_MORE_BUTTON_URL + widgetId, button.link)
|
||||||
|
.putString(PREF__WIDGET_MORE_BUTTON_TEXT + widgetId, button.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteWidget(widgetId: Int) {
|
||||||
|
preferences
|
||||||
|
.edit()
|
||||||
|
.remove(PREF__WIDGET_ID + widgetId)
|
||||||
|
.remove(PREF__WIDGET_TITLE + widgetId)
|
||||||
|
.remove(PREF__WIDGET_ICON + widgetId)
|
||||||
|
.remove(PREF__WIDGET_ROUND_ICON + widgetId)
|
||||||
|
.remove(PREF__WIDGET_USER + widgetId)
|
||||||
|
.remove(PREF__WIDGET_ADD_BUTTON_TEXT + widgetId)
|
||||||
|
.remove(PREF__WIDGET_ADD_BUTTON_URL + widgetId)
|
||||||
|
.remove(PREF__WIDGET_ADD_BUTTON_TYPE + widgetId)
|
||||||
|
.remove(PREF__WIDGET_MORE_BUTTON_TEXT + widgetId)
|
||||||
|
.remove(PREF__WIDGET_MORE_BUTTON_URL + widgetId)
|
||||||
|
.remove(PREF__WIDGET_MORE_BUTTON_TYPE + widgetId)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWidget(widgetId: Int): WidgetConfiguration {
|
||||||
|
val userOptional: Optional<User> =
|
||||||
|
userAccountManager.getUser(preferences.getString(PREF__WIDGET_USER + widgetId, ""))
|
||||||
|
|
||||||
|
val addButton = createAddButton(widgetId)
|
||||||
|
val moreButton = createMoreButton(widgetId)
|
||||||
|
|
||||||
|
return WidgetConfiguration(
|
||||||
|
preferences.getString(PREF__WIDGET_ID + widgetId, "") ?: "",
|
||||||
|
preferences.getString(PREF__WIDGET_TITLE + widgetId, "") ?: "",
|
||||||
|
preferences.getString(PREF__WIDGET_ICON + widgetId, "") ?: "",
|
||||||
|
preferences.getBoolean(PREF__WIDGET_ROUND_ICON + widgetId, false),
|
||||||
|
userOptional,
|
||||||
|
addButton,
|
||||||
|
moreButton
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAddButton(widgetId: Int): DashboardButton? {
|
||||||
|
var addButton: DashboardButton? = null
|
||||||
|
if (preferences.contains(PREF__WIDGET_ADD_BUTTON_TYPE + widgetId)) {
|
||||||
|
addButton = DashboardButton(
|
||||||
|
DashBoardButtonType.valueOf(
|
||||||
|
preferences.getString(
|
||||||
|
PREF__WIDGET_ADD_BUTTON_TYPE + widgetId,
|
||||||
|
""
|
||||||
|
) ?: ""
|
||||||
|
),
|
||||||
|
preferences.getString(PREF__WIDGET_ADD_BUTTON_TEXT + widgetId, "") ?: "",
|
||||||
|
preferences.getString(PREF__WIDGET_ADD_BUTTON_URL + widgetId, "") ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMoreButton(widgetId: Int): DashboardButton? {
|
||||||
|
var moreButton: DashboardButton? = null
|
||||||
|
if (preferences.contains(PREF__WIDGET_MORE_BUTTON_TYPE + widgetId)) {
|
||||||
|
moreButton = DashboardButton(
|
||||||
|
DashBoardButtonType.valueOf(
|
||||||
|
preferences.getString(
|
||||||
|
PREF__WIDGET_MORE_BUTTON_TYPE + widgetId,
|
||||||
|
""
|
||||||
|
) ?: ""
|
||||||
|
),
|
||||||
|
preferences.getString(PREF__WIDGET_MORE_BUTTON_TEXT + widgetId, "") ?: "",
|
||||||
|
preferences.getString(PREF__WIDGET_MORE_BUTTON_URL + widgetId, "") ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return moreButton
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PREF__WIDGET_TITLE = "widget_title_"
|
||||||
|
private const val PREF__WIDGET_ID = "widget_id_"
|
||||||
|
private const val PREF__WIDGET_ICON = "widget_icon_"
|
||||||
|
private const val PREF__WIDGET_ROUND_ICON = "widget_round_icon_"
|
||||||
|
private const val PREF__WIDGET_USER = "widget_user_"
|
||||||
|
private const val PREF__WIDGET_ADD_BUTTON_TEXT = "widget_add_button_text_"
|
||||||
|
private const val PREF__WIDGET_ADD_BUTTON_URL = "widget_add_button_url_"
|
||||||
|
private const val PREF__WIDGET_ADD_BUTTON_TYPE = "widget_add_button_type_"
|
||||||
|
private const val PREF__WIDGET_MORE_BUTTON_TEXT = "widget_more_button_text_"
|
||||||
|
private const val PREF__WIDGET_MORE_BUTTON_URL = "widget_more_button_url_"
|
||||||
|
private const val PREF__WIDGET_MORE_BUTTON_TYPE = "widget_more_button_type_"
|
||||||
|
}
|
||||||
|
}
|
|
@ -131,6 +131,7 @@ class ChooseAccountDialogFragment :
|
||||||
this,
|
this,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils
|
themeDrawableUtils
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,6 +81,24 @@ public class EmptyRecyclerView extends RecyclerView {
|
||||||
initEmptyView();
|
initEmptyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||||
|
super.onItemRangeChanged(positionStart, itemCount);
|
||||||
|
initEmptyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
|
||||||
|
super.onItemRangeChanged(positionStart, itemCount, payload);
|
||||||
|
initEmptyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||||
|
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
|
||||||
|
initEmptyView();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
super.onItemRangeInserted(positionStart, itemCount);
|
super.onItemRangeInserted(positionStart, itemCount);
|
||||||
|
|
|
@ -769,9 +769,7 @@ public abstract class DrawerActivity extends ToolbarActivity
|
||||||
this,
|
this,
|
||||||
firstQuota.getIconUrl(),
|
firstQuota.getIconUrl(),
|
||||||
target,
|
target,
|
||||||
R.drawable.ic_link,
|
R.drawable.ic_link);
|
||||||
size,
|
|
||||||
size);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mQuotaTextLink.setVisibility(View.GONE);
|
mQuotaTextLink.setVisibility(View.GONE);
|
||||||
|
@ -884,8 +882,6 @@ public abstract class DrawerActivity extends ToolbarActivity
|
||||||
if (mNavigationView != null && getBaseContext().getResources().getBoolean(R.bool.show_external_links)) {
|
if (mNavigationView != null && getBaseContext().getResources().getBoolean(R.bool.show_external_links)) {
|
||||||
mNavigationView.getMenu().removeGroup(R.id.drawer_menu_external_links);
|
mNavigationView.getMenu().removeGroup(R.id.drawer_menu_external_links);
|
||||||
|
|
||||||
float density = getResources().getDisplayMetrics().density;
|
|
||||||
final int size = Math.round(24 * density);
|
|
||||||
int greyColor = ContextCompat.getColor(this, R.color.drawer_menu_icon);
|
int greyColor = ContextCompat.getColor(this, R.color.drawer_menu_icon);
|
||||||
|
|
||||||
for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
|
for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
|
||||||
|
@ -911,9 +907,7 @@ public abstract class DrawerActivity extends ToolbarActivity
|
||||||
this,
|
this,
|
||||||
link.getIconUrl(),
|
link.getIconUrl(),
|
||||||
target,
|
target,
|
||||||
R.drawable.ic_link,
|
R.drawable.ic_link);
|
||||||
size,
|
|
||||||
size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDrawerMenuItemChecked(mCheckedMenuItem);
|
setDrawerMenuItemChecked(mCheckedMenuItem);
|
||||||
|
|
|
@ -155,6 +155,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
||||||
this,
|
this,
|
||||||
multipleAccountsSupported,
|
multipleAccountsSupported,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
|
|
||||||
|
@ -310,6 +311,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
||||||
this,
|
this,
|
||||||
multipleAccountsSupported,
|
multipleAccountsSupported,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
recyclerView.setAdapter(userListAdapter);
|
recyclerView.setAdapter(userListAdapter);
|
||||||
|
@ -364,6 +366,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
||||||
this,
|
this,
|
||||||
multipleAccountsSupported,
|
multipleAccountsSupported,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
recyclerView.setAdapter(userListAdapter);
|
recyclerView.setAdapter(userListAdapter);
|
||||||
|
|
|
@ -62,6 +62,7 @@ import android.widget.Toast;
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.nextcloud.client.account.User;
|
||||||
import com.nextcloud.client.di.Injectable;
|
import com.nextcloud.client.di.Injectable;
|
||||||
import com.nextcloud.client.preferences.AppPreferences;
|
import com.nextcloud.client.preferences.AppPreferences;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
|
@ -80,6 +81,7 @@ import com.owncloud.android.operations.UploadFileOperation;
|
||||||
import com.owncloud.android.syncadapter.FileSyncAdapter;
|
import com.owncloud.android.syncadapter.FileSyncAdapter;
|
||||||
import com.owncloud.android.ui.adapter.UploaderAdapter;
|
import com.owncloud.android.ui.adapter.UploaderAdapter;
|
||||||
import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
|
import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
|
||||||
|
import com.owncloud.android.ui.dialog.AccountChooserInterface;
|
||||||
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
|
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
|
||||||
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
|
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
|
||||||
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
|
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
|
||||||
|
@ -120,7 +122,6 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AlertDialog.Builder;
|
import androidx.appcompat.app.AlertDialog.Builder;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.core.view.MenuItemCompat;
|
import androidx.core.view.MenuItemCompat;
|
||||||
import androidx.core.widget.NestedScrollView;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
@ -131,8 +132,8 @@ import static com.owncloud.android.utils.DisplayUtils.openSortingOrderDialogFrag
|
||||||
* This can be used to upload things to an ownCloud instance.
|
* This can be used to upload things to an ownCloud instance.
|
||||||
*/
|
*/
|
||||||
public class ReceiveExternalFilesActivity extends FileActivity
|
public class ReceiveExternalFilesActivity extends FileActivity
|
||||||
implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
|
implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
|
||||||
SortingOrderDialogFragment.OnSortingOrderListener, Injectable {
|
SortingOrderDialogFragment.OnSortingOrderListener, Injectable, AccountChooserInterface {
|
||||||
|
|
||||||
private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
|
private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
@ -237,8 +238,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeAccount(Account account) {
|
@Override
|
||||||
setAccount(account, false);
|
public void onAccountChosen(@NonNull User user) {
|
||||||
|
setAccount(user.toPlatformAccount(), false);
|
||||||
initTargetFolder();
|
initTargetFolder();
|
||||||
populateDirectoryList();
|
populateDirectoryList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.owncloud.android.ui.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidget
|
||||||
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.nextcloud.client.widget.DashboardWidgetConfigurationInterface
|
||||||
|
import com.owncloud.android.databinding.WidgetListItemBinding
|
||||||
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
||||||
|
|
||||||
|
class DashboardWidgetListAdapter(
|
||||||
|
val themeDrawableUtils: ThemeDrawableUtils,
|
||||||
|
val accountManager: UserAccountManager,
|
||||||
|
val clientFactory: ClientFactory,
|
||||||
|
val context: Context,
|
||||||
|
private val dashboardWidgetConfigurationInterface: DashboardWidgetConfigurationInterface
|
||||||
|
) :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
private var widgets = emptyList<DashboardWidget>()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
return WidgetListItemViewHolder(
|
||||||
|
WidgetListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
themeDrawableUtils,
|
||||||
|
accountManager,
|
||||||
|
clientFactory,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
val widgetListItemViewHolder = holder as WidgetListItemViewHolder
|
||||||
|
|
||||||
|
widgetListItemViewHolder.bind(widgets[position], dashboardWidgetConfigurationInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return widgets.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun setWidgetList(list: Map<String, DashboardWidget>?) {
|
||||||
|
widgets = list?.map { (_, value) -> value }?.sortedBy { it.order } ?: emptyList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,13 +19,14 @@
|
||||||
|
|
||||||
package com.owncloud.android.ui.adapter;
|
package com.owncloud.android.ui.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.PictureDrawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
@ -147,7 +148,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
|
||||||
holder.binding.message.setText(notification.getMessage());
|
holder.binding.message.setText(notification.getMessage());
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(notification.getIcon())) {
|
if (!TextUtils.isEmpty(notification.getIcon())) {
|
||||||
downloadIcon(notification.getIcon(), holder.binding.icon);
|
downloadIcon(notification.getIcon(), holder.binding.icon, notificationsActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nightModeFlag =
|
int nightModeFlag =
|
||||||
|
@ -360,12 +361,12 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadIcon(String icon, ImageView itemViewType) {
|
private void downloadIcon(String icon, ImageView itemViewType, Context context) {
|
||||||
GenericRequestBuilder<Uri, InputStream, SVG, PictureDrawable> requestBuilder = Glide.with(notificationsActivity)
|
GenericRequestBuilder<Uri, InputStream, SVG, Drawable> requestBuilder = Glide.with(notificationsActivity)
|
||||||
.using(Glide.buildStreamModelLoader(Uri.class, notificationsActivity), InputStream.class)
|
.using(Glide.buildStreamModelLoader(Uri.class, notificationsActivity), InputStream.class)
|
||||||
.from(Uri.class)
|
.from(Uri.class)
|
||||||
.as(SVG.class)
|
.as(SVG.class)
|
||||||
.transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
|
.transcode(new SvgDrawableTranscoder(context), Drawable.class)
|
||||||
.sourceEncoder(new StreamEncoder())
|
.sourceEncoder(new StreamEncoder())
|
||||||
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder()))
|
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder()))
|
||||||
.decoder(new SvgDecoder())
|
.decoder(new SvgDecoder())
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
package com.owncloud.android.ui.adapter;
|
package com.owncloud.android.ui.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -40,7 +41,6 @@ import com.owncloud.android.databinding.AccountActionBinding;
|
||||||
import com.owncloud.android.databinding.AccountItemBinding;
|
import com.owncloud.android.databinding.AccountItemBinding;
|
||||||
import com.owncloud.android.lib.common.OwnCloudAccount;
|
import com.owncloud.android.lib.common.OwnCloudAccount;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
import com.owncloud.android.ui.activity.BaseActivity;
|
|
||||||
import com.owncloud.android.utils.DisplayUtils;
|
import com.owncloud.android.utils.DisplayUtils;
|
||||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
||||||
import com.owncloud.android.utils.theme.ThemeDrawableUtils;
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils;
|
||||||
|
@ -59,7 +59,7 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
private static final String TAG = UserListAdapter.class.getSimpleName();
|
private static final String TAG = UserListAdapter.class.getSimpleName();
|
||||||
|
|
||||||
private final float accountAvatarRadiusDimension;
|
private final float accountAvatarRadiusDimension;
|
||||||
private final BaseActivity context;
|
private final Context context;
|
||||||
private List<UserListItem> values;
|
private List<UserListItem> values;
|
||||||
private Listener accountListAdapterListener;
|
private Listener accountListAdapterListener;
|
||||||
private final UserAccountManager accountManager;
|
private final UserAccountManager accountManager;
|
||||||
|
@ -69,15 +69,17 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
private final ClickListener clickListener;
|
private final ClickListener clickListener;
|
||||||
private final boolean showAddAccount;
|
private final boolean showAddAccount;
|
||||||
private final boolean showDotsMenu;
|
private final boolean showDotsMenu;
|
||||||
|
private boolean highlightCurrentlyActiveAccount;
|
||||||
private final ThemeColorUtils themeColorUtils;
|
private final ThemeColorUtils themeColorUtils;
|
||||||
private final ThemeDrawableUtils themeDrawableUtils;
|
private final ThemeDrawableUtils themeDrawableUtils;
|
||||||
|
|
||||||
public UserListAdapter(BaseActivity context,
|
public UserListAdapter(Context context,
|
||||||
UserAccountManager accountManager,
|
UserAccountManager accountManager,
|
||||||
List<UserListItem> values,
|
List<UserListItem> values,
|
||||||
ClickListener clickListener,
|
ClickListener clickListener,
|
||||||
boolean showAddAccount,
|
boolean showAddAccount,
|
||||||
boolean showDotsMenu,
|
boolean showDotsMenu,
|
||||||
|
boolean highlightCurrentlyActiveAccount,
|
||||||
ThemeColorUtils themeColorUtils,
|
ThemeColorUtils themeColorUtils,
|
||||||
ThemeDrawableUtils themeDrawableUtils) {
|
ThemeDrawableUtils themeDrawableUtils) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -92,6 +94,7 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
this.showDotsMenu = showDotsMenu;
|
this.showDotsMenu = showDotsMenu;
|
||||||
this.themeColorUtils = themeColorUtils;
|
this.themeColorUtils = themeColorUtils;
|
||||||
this.themeDrawableUtils = themeDrawableUtils;
|
this.themeDrawableUtils = themeDrawableUtils;
|
||||||
|
this.highlightCurrentlyActiveAccount = highlightCurrentlyActiveAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,7 +128,7 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
if (UserListItem.TYPE_ACCOUNT == userListItem.getType()) {
|
if (UserListItem.TYPE_ACCOUNT == userListItem.getType()) {
|
||||||
final User user = userListItem.getUser();
|
final User user = userListItem.getUser();
|
||||||
AccountViewHolderItem item = (AccountViewHolderItem) holder;
|
AccountViewHolderItem item = (AccountViewHolderItem) holder;
|
||||||
item.bind(user, userListItem.isEnabled(), this);
|
item.bind(user, userListItem.isEnabled(), highlightCurrentlyActiveAccount, this);
|
||||||
} // create add account action item
|
} // create add account action item
|
||||||
else if (UserListItem.TYPE_ACTION_ADD == userListItem.getType() && accountListAdapterListener != null) {
|
else if (UserListItem.TYPE_ACTION_ADD == userListItem.getType() && accountListAdapterListener != null) {
|
||||||
((AddAccountViewHolderItem) holder).bind(accountListAdapterListener);
|
((AddAccountViewHolderItem) holder).bind(accountListAdapterListener);
|
||||||
|
@ -228,12 +231,19 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(User user, boolean userListItemEnabled, DisplayUtils.AvatarGenerationListener avatarGenerationListener) {
|
public void bind(User user,
|
||||||
|
boolean userListItemEnabled,
|
||||||
|
boolean highlightCurrentlyActiveAccount,
|
||||||
|
DisplayUtils.AvatarGenerationListener avatarGenerationListener) {
|
||||||
setData(user);
|
setData(user);
|
||||||
setUser(user);
|
setUser(user);
|
||||||
setUsername(user);
|
setUsername(user);
|
||||||
setAvatar(user, avatarGenerationListener);
|
setAvatar(user, avatarGenerationListener);
|
||||||
setCurrentlyActiveState(user);
|
if (highlightCurrentlyActiveAccount) {
|
||||||
|
setCurrentlyActiveState(user);
|
||||||
|
} else {
|
||||||
|
binding.ticker.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (!userListItemEnabled) {
|
if (!userListItemEnabled) {
|
||||||
binding.userName.setPaintFlags(binding.userName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
binding.userName.setPaintFlags(binding.userName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.owncloud.android.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.request.animation.GlideAnimation
|
||||||
|
import com.bumptech.glide.request.target.SimpleTarget
|
||||||
|
import com.nextcloud.android.lib.resources.dashboard.DashboardWidget
|
||||||
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.nextcloud.client.widget.DashboardWidgetConfigurationInterface
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.databinding.WidgetListItemBinding
|
||||||
|
import com.owncloud.android.utils.DisplayUtils
|
||||||
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
||||||
|
|
||||||
|
class WidgetListItemViewHolder(
|
||||||
|
val binding: WidgetListItemBinding,
|
||||||
|
val themeDrawableUtils: ThemeDrawableUtils,
|
||||||
|
val accountManager: UserAccountManager,
|
||||||
|
val clientFactory: ClientFactory,
|
||||||
|
val context: Context
|
||||||
|
) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(
|
||||||
|
dashboardWidget: DashboardWidget,
|
||||||
|
dashboardWidgetConfigurationInterface: DashboardWidgetConfigurationInterface
|
||||||
|
) {
|
||||||
|
binding.layout.setOnClickListener { dashboardWidgetConfigurationInterface.onItemClicked(dashboardWidget) }
|
||||||
|
|
||||||
|
val target = object : SimpleTarget<Drawable>() {
|
||||||
|
override fun onResourceReady(resource: Drawable?, glideAnimation: GlideAnimation<in Drawable>?) {
|
||||||
|
binding.icon.setImageDrawable(resource)
|
||||||
|
binding.icon.setColorFilter(context.getColor(R.color.dark), PorterDuff.Mode.SRC_ATOP)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(e: java.lang.Exception?, errorDrawable: Drawable?) {
|
||||||
|
super.onLoadFailed(e, errorDrawable)
|
||||||
|
binding.icon.setImageDrawable(errorDrawable)
|
||||||
|
binding.icon.setColorFilter(context.getColor(R.color.dark), PorterDuff.Mode.SRC_ATOP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayUtils.downloadIcon(
|
||||||
|
accountManager,
|
||||||
|
clientFactory,
|
||||||
|
context,
|
||||||
|
dashboardWidget.iconUrl,
|
||||||
|
target,
|
||||||
|
R.drawable.ic_dashboard
|
||||||
|
)
|
||||||
|
binding.name.text = dashboardWidget.title
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2022 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.owncloud.android.ui.dialog
|
||||||
|
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
|
||||||
|
interface AccountChooserInterface {
|
||||||
|
fun onAccountChosen(user: User)
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ package com.owncloud.android.ui.dialog;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -39,7 +40,6 @@ import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.nextcloud.client.di.Injectable;
|
import com.nextcloud.client.di.Injectable;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.databinding.MultipleAccountsBinding;
|
import com.owncloud.android.databinding.MultipleAccountsBinding;
|
||||||
import com.owncloud.android.ui.activity.ReceiveExternalFilesActivity;
|
|
||||||
import com.owncloud.android.ui.adapter.UserListAdapter;
|
import com.owncloud.android.ui.adapter.UserListAdapter;
|
||||||
import com.owncloud.android.ui.adapter.UserListItem;
|
import com.owncloud.android.ui.adapter.UserListItem;
|
||||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
||||||
|
@ -60,6 +60,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
||||||
@Inject UserAccountManager accountManager;
|
@Inject UserAccountManager accountManager;
|
||||||
@Inject ThemeColorUtils themeColorUtils;
|
@Inject ThemeColorUtils themeColorUtils;
|
||||||
@Inject ThemeDrawableUtils themeDrawableUtils;
|
@Inject ThemeDrawableUtils themeDrawableUtils;
|
||||||
|
public boolean highlightCurrentlyActiveAccount = true;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,7 +74,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
||||||
LayoutInflater inflater = activity.getLayoutInflater();
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
MultipleAccountsBinding binding = MultipleAccountsBinding.inflate(inflater, null, false);
|
MultipleAccountsBinding binding = MultipleAccountsBinding.inflate(inflater, null, false);
|
||||||
|
|
||||||
final ReceiveExternalFilesActivity parent = (ReceiveExternalFilesActivity) getActivity();
|
final Context parent = getActivity();
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
||||||
|
|
||||||
UserListAdapter adapter = new UserListAdapter(parent,
|
UserListAdapter adapter = new UserListAdapter(parent,
|
||||||
|
@ -81,6 +82,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
||||||
getAccountListItems(),
|
getAccountListItems(),
|
||||||
this,
|
this,
|
||||||
false,
|
false,
|
||||||
|
highlightCurrentlyActiveAccount,
|
||||||
false,
|
false,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
|
@ -125,9 +127,9 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAccountClicked(User user) {
|
public void onAccountClicked(User user) {
|
||||||
final ReceiveExternalFilesActivity parentActivity = (ReceiveExternalFilesActivity) getActivity();
|
final AccountChooserInterface parentActivity = (AccountChooserInterface) getActivity();
|
||||||
if (parentActivity != null) {
|
if (parentActivity != null) {
|
||||||
parentActivity.changeAccount(user.toPlatformAccount());
|
parentActivity.onAccountChosen(user);
|
||||||
}
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,9 +282,7 @@ class BackupListAdapter(
|
||||||
context,
|
context,
|
||||||
url,
|
url,
|
||||||
target,
|
target,
|
||||||
R.drawable.ic_user,
|
R.drawable.ic_user
|
||||||
imageView.width,
|
|
||||||
imageView.height
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,10 @@ import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffColorFilter;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
@ -47,6 +49,7 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
@ -429,7 +432,7 @@ public final class BitmapUtils {
|
||||||
imageView);
|
imageView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap createAvatarWithStatus(Bitmap avatar, StatusType statusType, String icon, Context context) {
|
public static Bitmap createAvatarWithStatus(Bitmap avatar, StatusType statusType, @NonNull String icon, Context context) {
|
||||||
float avatarRadius = getResources().getDimension(R.dimen.list_item_avatar_icon_radius);
|
float avatarRadius = getResources().getDimension(R.dimen.list_item_avatar_icon_radius);
|
||||||
int width = DisplayUtils.convertDpToPixel(2 * avatarRadius, context);
|
int width = DisplayUtils.convertDpToPixel(2 * avatarRadius, context);
|
||||||
|
|
||||||
|
@ -453,6 +456,42 @@ public final class BitmapUtils {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired from https://www.demo2s.com/android/android-bitmap-get-a-round-version-of-the-bitmap.html
|
||||||
|
*/
|
||||||
|
public static Bitmap roundBitmap(Bitmap bitmap) {
|
||||||
|
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
|
final Canvas canvas = new Canvas(output);
|
||||||
|
|
||||||
|
final int color = R.color.white;
|
||||||
|
final Paint paint = new Paint();
|
||||||
|
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||||
|
final RectF rectF = new RectF(rect);
|
||||||
|
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
canvas.drawARGB(0, 0, 0, 0);
|
||||||
|
paint.setColor(getResources().getColor(color, null));
|
||||||
|
canvas.drawOval(rectF, paint);
|
||||||
|
|
||||||
|
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||||
|
canvas.drawBitmap(bitmap, rect, rect, paint);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* from https://stackoverflow.com/a/38249623
|
||||||
|
**/
|
||||||
|
public static Bitmap tintImage(Bitmap bitmap, int color) {
|
||||||
|
Paint paint = new Paint();
|
||||||
|
paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
|
||||||
|
Bitmap bitmapResult = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmapResult);
|
||||||
|
canvas.drawBitmap(bitmap, 0, 0, paint);
|
||||||
|
return bitmapResult;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* from https://stackoverflow.com/a/12089127
|
* from https://stackoverflow.com/a/12089127
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,7 +36,6 @@ import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.PictureDrawable;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
|
@ -142,6 +141,7 @@ public final class DisplayUtils {
|
||||||
public static final String MONTH_YEAR_PATTERN = "MMMM yyyy";
|
public static final String MONTH_YEAR_PATTERN = "MMMM yyyy";
|
||||||
public static final String MONTH_PATTERN = "MMMM";
|
public static final String MONTH_PATTERN = "MMMM";
|
||||||
public static final String YEAR_PATTERN = "yyyy";
|
public static final String YEAR_PATTERN = "yyyy";
|
||||||
|
public static final int SVG_SIZE = 512;
|
||||||
|
|
||||||
private static Map<String, String> mimeType2HumanReadable;
|
private static Map<String, String> mimeType2HumanReadable;
|
||||||
|
|
||||||
|
@ -552,13 +552,10 @@ public final class DisplayUtils {
|
||||||
Context context,
|
Context context,
|
||||||
String iconUrl,
|
String iconUrl,
|
||||||
SimpleTarget imageView,
|
SimpleTarget imageView,
|
||||||
int placeholder,
|
int placeholder) {
|
||||||
int width,
|
|
||||||
int height) {
|
|
||||||
try {
|
try {
|
||||||
if (iconUrl.endsWith(".svg")) {
|
if (Uri.parse(iconUrl).getEncodedPath().endsWith(".svg")) {
|
||||||
downloadSVGIcon(currentAccountProvider, clientFactory, context, iconUrl, imageView, placeholder, width,
|
downloadSVGIcon(currentAccountProvider, clientFactory, context, iconUrl, imageView, placeholder);
|
||||||
height);
|
|
||||||
} else {
|
} else {
|
||||||
downloadPNGIcon(context, iconUrl, imageView, placeholder);
|
downloadPNGIcon(context, iconUrl, imageView, placeholder);
|
||||||
}
|
}
|
||||||
|
@ -583,17 +580,15 @@ public final class DisplayUtils {
|
||||||
Context context,
|
Context context,
|
||||||
String iconUrl,
|
String iconUrl,
|
||||||
SimpleTarget imageView,
|
SimpleTarget imageView,
|
||||||
int placeholder,
|
int placeholder) {
|
||||||
int width,
|
GenericRequestBuilder<Uri, InputStream, SVG, Drawable> requestBuilder = Glide.with(context)
|
||||||
int height) {
|
|
||||||
GenericRequestBuilder<Uri, InputStream, SVG, PictureDrawable> requestBuilder = Glide.with(context)
|
|
||||||
.using(new CustomGlideUriLoader(currentAccountProvider.getUser(), clientFactory), InputStream.class)
|
.using(new CustomGlideUriLoader(currentAccountProvider.getUser(), clientFactory), InputStream.class)
|
||||||
.from(Uri.class)
|
.from(Uri.class)
|
||||||
.as(SVG.class)
|
.as(SVG.class)
|
||||||
.transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
|
.transcode(new SvgDrawableTranscoder(context), Drawable.class)
|
||||||
.sourceEncoder(new StreamEncoder())
|
.sourceEncoder(new StreamEncoder())
|
||||||
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder(height, width)))
|
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder()))
|
||||||
.decoder(new SvgDecoder(height, width))
|
.decoder(new SvgDecoder())
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
.error(placeholder)
|
.error(placeholder)
|
||||||
.animate(android.R.anim.fade_in);
|
.animate(android.R.anim.fade_in);
|
||||||
|
|
|
@ -25,14 +25,6 @@ import java.io.InputStream;
|
||||||
* Decodes an SVG internal representation from an {@link InputStream}.
|
* Decodes an SVG internal representation from an {@link InputStream}.
|
||||||
*/
|
*/
|
||||||
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
|
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
|
||||||
private int height = -1;
|
|
||||||
private int width = -1;
|
|
||||||
|
|
||||||
public SvgDecoder(int height, int width) {
|
|
||||||
this.height = height;
|
|
||||||
this.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SvgDecoder() {
|
public SvgDecoder() {
|
||||||
// empty constructor
|
// empty constructor
|
||||||
}
|
}
|
||||||
|
@ -40,13 +32,9 @@ public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
|
||||||
public Resource<SVG> decode(InputStream source, int w, int h) throws IOException {
|
public Resource<SVG> decode(InputStream source, int w, int h) throws IOException {
|
||||||
try {
|
try {
|
||||||
SVG svg = SVG.getFromInputStream(source);
|
SVG svg = SVG.getFromInputStream(source);
|
||||||
|
svg.setDocumentViewBox(0, 0, svg.getDocumentWidth(), svg.getDocumentHeight());
|
||||||
if (width > 0) {
|
svg.setDocumentWidth("100%");
|
||||||
svg.setDocumentWidth(width);
|
svg.setDocumentHeight("100%");
|
||||||
}
|
|
||||||
if (height > 0) {
|
|
||||||
svg.setDocumentHeight(height);
|
|
||||||
}
|
|
||||||
svg.setDocumentPreserveAspectRatio(PreserveAspectRatio.LETTERBOX);
|
svg.setDocumentPreserveAspectRatio(PreserveAspectRatio.LETTERBOX);
|
||||||
|
|
||||||
return new SimpleResource<>(svg);
|
return new SimpleResource<>(svg);
|
||||||
|
|
|
@ -10,7 +10,12 @@
|
||||||
*/
|
*/
|
||||||
package com.owncloud.android.utils.svg;
|
package com.owncloud.android.utils.svg;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Picture;
|
import android.graphics.Picture;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.PictureDrawable;
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.Resource;
|
import com.bumptech.glide.load.engine.Resource;
|
||||||
|
@ -21,13 +26,27 @@ import com.caverock.androidsvg.SVG;
|
||||||
/**
|
/**
|
||||||
* Convert the {@link SVG}'s internal representation to an Android-compatible one ({@link Picture}).
|
* Convert the {@link SVG}'s internal representation to an Android-compatible one ({@link Picture}).
|
||||||
*/
|
*/
|
||||||
public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> {
|
public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, Drawable> {
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public SvgDrawableTranscoder(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource<PictureDrawable> transcode(Resource<SVG> toTranscode) {
|
public Resource<Drawable> transcode(Resource<SVG> toTranscode) {
|
||||||
SVG svg = toTranscode.get();
|
SVG svg = toTranscode.get();
|
||||||
Picture picture = svg.renderToPicture();
|
Picture picture = svg.renderToPicture();
|
||||||
PictureDrawable drawable = new PictureDrawable(picture);
|
PictureDrawable drawable = new PictureDrawable(picture);
|
||||||
return new SimpleResource<PictureDrawable>(drawable);
|
|
||||||
|
Bitmap returnedBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
|
||||||
|
drawable.getIntrinsicHeight(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(returnedBitmap);
|
||||||
|
canvas.drawPicture(drawable.getPicture());
|
||||||
|
Drawable d = new BitmapDrawable(context.getResources(), returnedBitmap);
|
||||||
|
|
||||||
|
return new SimpleResource<>(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
*/
|
*/
|
||||||
package com.owncloud.android.utils.svg;
|
package com.owncloud.android.utils.svg;
|
||||||
|
|
||||||
import android.graphics.drawable.PictureDrawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@ import com.bumptech.glide.request.RequestListener;
|
||||||
import com.bumptech.glide.request.target.ImageViewTarget;
|
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
|
||||||
public class SvgSoftwareLayerSetter<T> implements RequestListener<T, PictureDrawable> {
|
public class SvgSoftwareLayerSetter<T> implements RequestListener<T, Drawable> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onException(Exception e, T model, Target<PictureDrawable> target, boolean isFirstResource) {
|
public boolean onException(Exception e, T model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
ImageView view = ((ImageViewTarget<?>) target).getView();
|
ImageView view = ((ImageViewTarget<?>) target).getView();
|
||||||
if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
|
if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
|
||||||
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
|
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
|
||||||
|
@ -30,7 +30,7 @@ public class SvgSoftwareLayerSetter<T> implements RequestListener<T, PictureDraw
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onResourceReady(PictureDrawable resource, T model, Target<PictureDrawable> target,
|
public boolean onResourceReady(Drawable resource, T model, Target<Drawable> target,
|
||||||
boolean isFromMemoryCache, boolean isFirstResource) {
|
boolean isFromMemoryCache, boolean isFirstResource) {
|
||||||
ImageView view = ((ImageViewTarget<?>) target).getView();
|
ImageView view = ((ImageViewTarget<?>) target).getView();
|
||||||
if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
|
if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
|
||||||
|
|
11
app/src/main/res/drawable/ic_check.xml
Normal file
11
app/src/main/res/drawable/ic_check.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector android:autoMirrored="true"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#000000"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||||
|
</vector>
|
35
app/src/main/res/drawable/ic_dashboard.xml
Normal file
35
app/src/main/res/drawable/ic_dashboard.xml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:autoMirrored="true"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportHeight="16"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:width="16dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="m7.906,1a7,7 0,0 0,-6.906 7,7 7,0 0,0 7,7 7,7 0,0 0,7 -7,7 7,0 0,0 -7,-7 7,7 0,0 0,-0.094 0zM8,3.699a4.3,4.3 0,0 1,4.301 4.301,4.3 4.3,0 0,1 -4.301,4.301 4.3,4.3 0,0 1,-4.301 -4.301,4.3 4.3,0 0,1 4.301,-4.301z"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth=".5" />
|
||||||
|
</vector>
|
142
app/src/main/res/layout/dashboard_widget.xml
Normal file
142
app/src/main/res/layout/dashboard_widget.xml
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@android:id/background"
|
||||||
|
style="@style/Widget.Nextcloud.AppWidget.Container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:backgroundTint="@color/grey_200"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:theme="@style/Theme.ownCloud.Toolbar.AppWidgetContainer">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginStart="@dimen/standard_margin"
|
||||||
|
android:layout_marginTop="@dimen/standard_margin"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:layout_marginBottom="@dimen/standard_margin"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||||
|
android:contentDescription="@string/icon_of_dashboard_widget"
|
||||||
|
android:src="@drawable/ic_dashboard" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/create"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:contentDescription="@string/create_new"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:src="@drawable/ic_plus" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reload"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:contentDescription="@string/refresh_content"
|
||||||
|
android:src="@drawable/ic_sync" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:divider="@null"
|
||||||
|
android:layout_marginStart="@dimen/standard_margin"
|
||||||
|
android:layout_marginEnd="@dimen/zero" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/empty_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:layout_margin="@dimen/standard_half_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:paddingTop="@dimen/standard_margin">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/empty_list_icon"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:src="@drawable/ic_check"
|
||||||
|
android:contentDescription="@string/icon_for_empty_list" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_list_view_headline"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:paddingTop="@dimen/standard_half_padding"
|
||||||
|
android:paddingBottom="@dimen/standard_half_padding"
|
||||||
|
android:text="@string/no_items"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_list_view_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:paddingTop="@dimen/standard_half_padding"
|
||||||
|
android:paddingBottom="@dimen/standard_half_padding"
|
||||||
|
android:text="@string/check_back_later_or_reload" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@id/layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="@dimen/standard_margin">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:contentDescription="@string/icon_of_dashboard_widget"
|
||||||
|
app:srcCompat="@drawable/ic_dashboard" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/chooseWidget"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/choose_widget"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/accountName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.owncloud.android.ui.EmptyRecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/empty_view"
|
||||||
|
layout="@layout/empty_list" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/close"
|
||||||
|
style="@style/OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_margin="@dimen/standard_margin"
|
||||||
|
android:text="@string/common_cancel"
|
||||||
|
app:cornerRadius="@dimen/button_corner_radius" />
|
||||||
|
</LinearLayout>
|
68
app/src/main/res/layout/widget_item.xml
Normal file
68
app/src/main/res/layout/widget_item.xml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingEnd="@dimen/standard_half_margin"
|
||||||
|
android:paddingStart="@dimen/zero"
|
||||||
|
android:layout_height="48dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||||
|
android:src="@drawable/ic_user"
|
||||||
|
android:contentDescription="@string/icon_of_widget_entry" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/text_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="start"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
tools:text="First line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start"
|
||||||
|
android:textColor="@color/standard_grey"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="Subline" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
37
app/src/main/res/layout/widget_item_load_more.xml
Normal file
37
app/src/main/res/layout/widget_item_load_more.xml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/standard_margin"
|
||||||
|
android:id="@+id/load_more_container">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/load_more"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textColor="@color/standard_grey" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
47
app/src/main/res/layout/widget_list_item.xml
Normal file
47
app/src/main/res/layout/widget_list_item.xml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@id/layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="@dimen/standard_margin"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:contentDescription="@string/icon_of_dashboard_widget"
|
||||||
|
app:srcCompat="@drawable/ic_dashboard" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
tools:text="Widget name" />
|
||||||
|
</LinearLayout>
|
|
@ -1,16 +1,16 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string-array name="pref_behaviour_entries" translatable="false">
|
<string-array name="pref_behaviour_entries" translatable="false">
|
||||||
<item>@string/pref_behaviour_entries_keep_file</item>
|
<item>@string/pref_behaviour_entries_keep_file</item>
|
||||||
<item>@string/pref_behaviour_entries_move</item>
|
<item>@string/pref_behaviour_entries_move</item>
|
||||||
<item>@string/pref_behaviour_entries_delete_file</item>
|
<item>@string/pref_behaviour_entries_delete_file</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_behaviour_entryValues" translatable="false">
|
<string-array name="pref_behaviour_entryValues" translatable="false">
|
||||||
<item>LOCAL_BEHAVIOUR_FORGET</item>
|
<item>LOCAL_BEHAVIOUR_FORGET</item>
|
||||||
<item>LOCAL_BEHAVIOUR_MOVE</item>
|
<item>LOCAL_BEHAVIOUR_MOVE</item>
|
||||||
<item>LOCAL_BEHAVIOUR_DELETE</item>
|
<item>LOCAL_BEHAVIOUR_DELETE</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_name_collision_policy_entries" translatable="false">
|
<string-array name="pref_name_collision_policy_entries" translatable="false">
|
||||||
<item>@string/pref_instant_name_collision_policy_entries_always_ask</item>
|
<item>@string/pref_instant_name_collision_policy_entries_always_ask</item>
|
||||||
|
@ -29,4 +29,9 @@
|
||||||
<item>@string/link_share_view_only</item>
|
<item>@string/link_share_view_only</item>
|
||||||
<item>@string/link_share_editing</item>
|
<item>@string/link_share_editing</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<declare-styleable name="AppWidgetAttrs">
|
||||||
|
<attr name="appWidgetPadding" format="dimension" />
|
||||||
|
<attr name="appWidgetInnerRadius" format="dimension" />
|
||||||
|
<attr name="appWidgetRadius" format="dimension" />
|
||||||
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
Nextcloud Android client application
|
Nextcloud Android client application
|
||||||
|
|
||||||
Copyright (C) 2012 Bartek Przybylski
|
Copyright (C) 2012 Bartek Przybylski
|
||||||
|
|
|
@ -955,8 +955,8 @@
|
||||||
<string name="dnd">Do not disturb</string>
|
<string name="dnd">Do not disturb</string>
|
||||||
<string name="away">Away</string>
|
<string name="away">Away</string>
|
||||||
<string name="invisible">Invisible</string>
|
<string name="invisible">Invisible</string>
|
||||||
<string translatable="false" name="divider">—</string>
|
<string name="divider" translatable="false">—</string>
|
||||||
<string translatable="false" name="default_emoji">😃</string>
|
<string name="default_emoji" translatable="false">😃</string>
|
||||||
<string name="dontClear">Don\'t clear</string>
|
<string name="dontClear">Don\'t clear</string>
|
||||||
<string name="today">Today</string>
|
<string name="today">Today</string>
|
||||||
<string name="thirtyMinutes">30 minutes</string>
|
<string name="thirtyMinutes">30 minutes</string>
|
||||||
|
@ -1041,4 +1041,15 @@
|
||||||
<string name="file_already_exists">Filename already exists</string>
|
<string name="file_already_exists">Filename already exists</string>
|
||||||
<string name="filedetails_export">Export</string>
|
<string name="filedetails_export">Export</string>
|
||||||
<string name="locate_folder">Locate folder</string>
|
<string name="locate_folder">Locate folder</string>
|
||||||
|
<string name="app_widget_description">Shows one widget from dashboard</string>
|
||||||
|
<string name="icon_of_dashboard_widget">Icon of dashboard widget</string>
|
||||||
|
<string name="refresh_content">Refresh content</string>
|
||||||
|
<string name="icon_of_widget_entry">Icon of widget entry</string>
|
||||||
|
<string name="choose_widget">Choose widget</string>
|
||||||
|
<string name="reload">Reload</string>
|
||||||
|
<string name="widgets_not_available">Widgets are only available on %1$s 25 or later</string>
|
||||||
|
<string name="widgets_not_available_title">Not available</string>
|
||||||
|
<string name="icon_for_empty_list">icon for empty list</string>
|
||||||
|
<string name="no_items">No items</string>
|
||||||
|
<string name="check_back_later_or_reload">Check back later or reload.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
ownCloud Android client application
|
ownCloud Android client application
|
||||||
|
|
||||||
Copyright (C) 2012 Bartek Przybylski
|
Copyright (C) 2012 Bartek Przybylski
|
||||||
|
@ -101,8 +100,7 @@
|
||||||
<item name="android:buttonBarButtonStyle">@style/FallbackTheming.Dialog.ButtonStyle</item>
|
<item name="android:buttonBarButtonStyle">@style/FallbackTheming.Dialog.ButtonStyle</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="FallbackTheming.Dialog.ButtonStyle"
|
<style name="FallbackTheming.Dialog.ButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||||
parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
|
||||||
<item name="android:textColor">@color/text_color</item>
|
<item name="android:textColor">@color/text_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -283,8 +281,7 @@
|
||||||
<item name="colorAccent">@color/color_accent</item>
|
<item name="colorAccent">@color/color_accent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.ownCloud.Widget.ActionBar"
|
<style name="Theme.ownCloud.Widget.ActionBar" parent="@style/Theme.MaterialComponents.Light.DarkActionBar.Bridge">
|
||||||
parent="@style/Theme.MaterialComponents.Light.DarkActionBar.Bridge">
|
|
||||||
<item name="android:background">@color/primary</item>
|
<item name="android:background">@color/primary</item>
|
||||||
<item name="background">@color/primary</item>
|
<item name="background">@color/primary</item>
|
||||||
<item name="android:textColor">@color/text_color</item>
|
<item name="android:textColor">@color/text_color</item>
|
||||||
|
@ -339,8 +336,8 @@
|
||||||
<item name="android:textSize">26sp</item>
|
<item name="android:textSize">26sp</item>
|
||||||
<item name="android:textColor">@color/text_color</item>
|
<item name="android:textColor">@color/text_color</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="NextcloudTextAppearanceMedium" parent="@style/TextAppearance.AppCompat.Medium">
|
|
||||||
</style>
|
<style name="NextcloudTextAppearanceMedium" parent="@style/TextAppearance.AppCompat.Medium"></style>
|
||||||
|
|
||||||
<style name="Widget.App.Login.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
<style name="Widget.App.Login.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
<item name="materialThemeOverlay">@style/ThemeOverlay.App.Login.TextInputLayout</item>
|
<item name="materialThemeOverlay">@style/ThemeOverlay.App.Login.TextInputLayout</item>
|
||||||
|
@ -447,4 +444,8 @@
|
||||||
<item name="android:background">@drawable/ripple</item>
|
<item name="android:background">@drawable/ripple</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Nextcloud.AppWidget.Container" parent="android:Widget">
|
||||||
|
<item name="android:id">@android:id/background</item>
|
||||||
|
<item name="android:background">?android:attr/colorBackground</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
39
app/src/main/res/values/themes.xml
Normal file
39
app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.ownCloud.Toolbar.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
|
||||||
|
<!-- Radius of the outer bound of widgets to make the rounded corners -->
|
||||||
|
<item name="appWidgetRadius">16dp</item>
|
||||||
|
<!--
|
||||||
|
Radius of the inner view's bound of widgets to make the rounded corners.
|
||||||
|
It needs to be 8dp or less than the value of appWidgetRadius
|
||||||
|
-->
|
||||||
|
<item name="appWidgetInnerRadius">8dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.ownCloud.Toolbar.AppWidgetContainer" parent="Theme.ownCloud.Toolbar.AppWidgetContainerParent">
|
||||||
|
<!-- Apply padding to avoid the content of the widget colliding with the rounded corners -->
|
||||||
|
<item name="appWidgetPadding">0dp</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
32
app/src/main/res/xml/dashboard_widget_info.xml
Normal file
32
app/src/main/res/xml/dashboard_widget_info.xml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~
|
||||||
|
~ Nextcloud Android client application
|
||||||
|
~
|
||||||
|
~ @author Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 Tobias Kaminsky
|
||||||
|
~ Copyright (C) 2022 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:description="@string/app_widget_description"
|
||||||
|
android:initialKeyguardLayout="@layout/dashboard_widget"
|
||||||
|
android:initialLayout="@layout/dashboard_widget"
|
||||||
|
android:minWidth="270dp"
|
||||||
|
android:minHeight="180dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:widgetCategory="home_screen"
|
||||||
|
android:configure="com.nextcloud.client.widget.DashboardWidgetConfigurationActivity" />
|
|
@ -3,7 +3,7 @@ package com.nextcloud.client.preferences;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -128,7 +128,7 @@ public class TestAppPreferences {
|
||||||
private SharedPreferences.Editor editor;
|
private SharedPreferences.Editor editor;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CurrentAccountProvider accountProvider;
|
private UserAccountManager userAccountManager;
|
||||||
|
|
||||||
private AppPreferencesImpl appPreferences;
|
private AppPreferencesImpl appPreferences;
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ public class TestAppPreferences {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
when(editor.remove(anyString())).thenReturn(editor);
|
when(editor.remove(anyString())).thenReturn(editor);
|
||||||
when(sharedPreferences.edit()).thenReturn(editor);
|
when(sharedPreferences.edit()).thenReturn(editor);
|
||||||
appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, accountProvider);
|
appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, userAccountManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -73,6 +73,7 @@ public class UserListAdapterTest {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
assertEquals(0, userListAdapter.getItemCount());
|
assertEquals(0, userListAdapter.getItemCount());
|
||||||
|
@ -93,6 +94,7 @@ public class UserListAdapterTest {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
|
|
||||||
|
@ -115,6 +117,7 @@ public class UserListAdapterTest {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
themeColorUtils,
|
themeColorUtils,
|
||||||
themeDrawableUtils);
|
themeDrawableUtils);
|
||||||
|
|
||||||
|
|
15
drawable_resources/dashboard.svg
Normal file
15
drawable_resources/dashboard.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="16" height="16" version="1.1" id="svg4" sodipodi:docname="dashboard.svg"
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs8" />
|
||||||
|
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050"
|
||||||
|
showgrid="false" inkscape:zoom="46.4375" inkscape:cx="8" inkscape:cy="7.9892328" inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1141" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<path
|
||||||
|
d="m7.9062 1a7 7 0 0 0-6.9062 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-0.09375 0zm0.09375 2.6992a4.3 4.3 0 0 1 4.3008 4.3008 4.3 4.3 0 0 1-4.3008 4.3008 4.3 4.3 0 0 1-4.3008-4.3008 4.3 4.3 0 0 1 4.3008-4.3008z"
|
||||||
|
fill="#fff" stroke-dashoffset="10" stroke-linecap="round" stroke-linejoin="round" stroke-width=".5"
|
||||||
|
style="paint-order:markers stroke fill;fill:#000000" id="path2" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in a new issue