mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 07:05:49 +03:00
commit
2cbd6d9659
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
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
|
@ -151,6 +149,13 @@
|
|||
<activity
|
||||
android:name=".ui.activity.SyncedFoldersActivity"
|
||||
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
|
||||
android:name="com.nextcloud.client.jobs.MediaFoldersDetectionWork$NotificationReceiver"
|
||||
|
@ -158,6 +163,17 @@
|
|||
<receiver
|
||||
android:name="com.nextcloud.client.jobs.NotificationWork$NotificationReceiver"
|
||||
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
|
||||
android:name=".ui.activity.UploadFilesActivity"
|
||||
|
@ -220,7 +236,6 @@
|
|||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".syncadapter.FileSyncService"
|
||||
android:exported="true"
|
||||
|
@ -233,6 +248,10 @@
|
|||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_files" />
|
||||
</service>
|
||||
<service
|
||||
android:name="com.nextcloud.client.widget.DashboardWidgetService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS"
|
||||
android:exported="true" />
|
||||
|
||||
<provider
|
||||
android:name=".providers.FileContentProvider"
|
||||
|
@ -304,16 +323,12 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/exposed_filepaths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name=".providers.DiskLruImageCacheFileProvider"
|
||||
android:authorities="@string/image_cache_provider_authority"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
|
||||
<!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"></provider> <!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
|
||||
<!-- to "best before" dates in his fridge. -->
|
||||
<!-- disable default provider -->
|
||||
<provider
|
||||
|
@ -327,8 +342,6 @@
|
|||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".authentication.AuthenticatorActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
|
@ -341,7 +354,6 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".authentication.DeepLinkLoginActivity"
|
||||
android:clearTaskOnLaunch="true"
|
||||
|
@ -391,11 +403,9 @@
|
|||
<activity
|
||||
android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.nextcloud.client.logger.ui.LogsActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.nextcloud.client.errorhandling.ShowErrorActivity"
|
||||
android:excludeFromRecents="true"
|
||||
|
@ -465,7 +475,6 @@
|
|||
android:label="@string/manage_space_title"
|
||||
android:theme="@style/Theme.ownCloud" />
|
||||
|
||||
|
||||
<service
|
||||
android:name=".services.AccountManagerService"
|
||||
android:enabled="true"
|
||||
|
@ -476,12 +485,10 @@
|
|||
android:name=".ui.activity.SsoGrantPermissionActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.ownCloud.Dialog.NoTitle" />
|
||||
|
||||
<activity
|
||||
android:name="com.nextcloud.client.etm.EtmActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.ownCloud.Toolbar" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.preview.PreviewBitmapActivity"
|
||||
android:exported="false"
|
||||
|
|
|
@ -29,6 +29,9 @@ import com.nextcloud.client.media.PlayerService;
|
|||
import com.nextcloud.client.migrations.Migrations;
|
||||
import com.nextcloud.client.onboarding.FirstRunActivity;
|
||||
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.SetStatusDialogFragment;
|
||||
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.GalleryFragment;
|
||||
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.OCFileListBottomSheetDialogFragment;
|
||||
import com.owncloud.android.ui.fragment.OCFileListFragment;
|
||||
import com.owncloud.android.ui.fragment.SharedListFragment;
|
||||
import com.owncloud.android.ui.fragment.UnifiedSearchFragment;
|
||||
|
@ -341,6 +344,9 @@ abstract class ComponentsModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract FileSyncService fileSyncService();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract DashboardWidgetService dashboardWidgetService();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract PreviewPdfFragment previewPDFFragment();
|
||||
|
||||
|
@ -430,4 +436,10 @@ abstract class ComponentsModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
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;
|
||||
|
||||
class ClientFactoryImpl implements ClientFactory {
|
||||
public class ClientFactoryImpl implements ClientFactory {
|
||||
|
||||
private Context context;
|
||||
|
||||
ClientFactoryImpl(Context context) {
|
||||
public ClientFactoryImpl(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
@ -49,8 +49,8 @@ class ClientFactoryImpl implements ClientFactory {
|
|||
public OwnCloudClient create(User user) throws CreationException {
|
||||
try {
|
||||
return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context);
|
||||
} catch (OperationCanceledException|
|
||||
AuthenticatorException|
|
||||
} catch (OperationCanceledException |
|
||||
AuthenticatorException |
|
||||
IOException e) {
|
||||
throw new CreationException(e);
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ import android.annotation.SuppressLint;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
import com.nextcloud.client.account.UserAccountManagerImpl;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
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}.
|
||||
*
|
||||
* Users should not use this class directly. Please use {@link AppPreferences} interface
|
||||
* instead.
|
||||
* <p>
|
||||
* Users should not use this class directly. Please use {@link AppPreferences} interface instead.
|
||||
*/
|
||||
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.
|
||||
* Value handled by the app without direct access in the UI.
|
||||
* Constant to access value of last path selected by the user to upload a file shared from other app. Value handled
|
||||
* by the app without direct access in the UI.
|
||||
*/
|
||||
public static final String AUTO_PREF__LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
|
||||
public static final String STORAGE_PATH = "storage_path";
|
||||
|
@ -101,7 +100,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
|
||||
private final Context context;
|
||||
private final SharedPreferences preferences;
|
||||
private final CurrentAccountProvider currentAccountProvider;
|
||||
private final UserAccountManager userAccountManager;
|
||||
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) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
@ -133,7 +132,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PREF__DARK_THEME.equals(key)) {
|
||||
DarkMode mode = preferences.getDarkThemeMode();
|
||||
for(Listener l : listeners) {
|
||||
for (Listener l : listeners) {
|
||||
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
|
||||
* dependency injection yet. Use injected component via {@link AppPreferences} interface.
|
||||
*
|
||||
* This is a temporary workaround to access app preferences in places that cannot use dependency injection yet. Use
|
||||
* injected component via {@link AppPreferences} interface.
|
||||
* <p>
|
||||
* WARNING: this creates new instance! it does not return app-wide singleton
|
||||
*
|
||||
* @param context Context used to create shared preferences
|
||||
|
@ -151,15 +150,15 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
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.preferences = preferences;
|
||||
this.currentAccountProvider = currentAccountProvider;
|
||||
this.userAccountManager = userAccountManager;
|
||||
this.listeners = new ListenerRegistry(this);
|
||||
this.preferences.registerOnSharedPreferenceChangeListener(listeners);
|
||||
}
|
||||
|
@ -277,7 +276,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
|
||||
@Override
|
||||
public String[] getPassCode() {
|
||||
return new String[] {
|
||||
return new String[]{
|
||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D1, null),
|
||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D2, null),
|
||||
preferences.getString(PassCodeActivity.PREFERENCE_PASSCODE_D3, null),
|
||||
|
@ -293,7 +292,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
@Override
|
||||
public String getFolderLayout(OCFile folder) {
|
||||
return getFolderPreference(context,
|
||||
currentAccountProvider.getUser(),
|
||||
userAccountManager.getUser(),
|
||||
PREF__FOLDER_LAYOUT,
|
||||
folder,
|
||||
FOLDER_LAYOUT_LIST);
|
||||
|
@ -302,7 +301,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
@Override
|
||||
public void setFolderLayout(@Nullable OCFile folder, String layoutName) {
|
||||
setFolderPreference(context,
|
||||
currentAccountProvider.getUser(),
|
||||
userAccountManager.getUser(),
|
||||
PREF__FOLDER_LAYOUT,
|
||||
folder,
|
||||
layoutName);
|
||||
|
@ -311,7 +310,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
@Override
|
||||
public FileSortOrder getSortOrderByFolder(OCFile folder) {
|
||||
return FileSortOrder.sortOrders.get(getFolderPreference(context,
|
||||
currentAccountProvider.getUser(),
|
||||
userAccountManager.getUser(),
|
||||
PREF__FOLDER_SORT_ORDER,
|
||||
folder,
|
||||
FileSortOrder.sort_a_to_z.name));
|
||||
|
@ -320,7 +319,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
@Override
|
||||
public void setSortOrder(@Nullable OCFile folder, FileSortOrder sortOrder) {
|
||||
setFolderPreference(context,
|
||||
currentAccountProvider.getUser(),
|
||||
userAccountManager.getUser(),
|
||||
PREF__FOLDER_SORT_ORDER,
|
||||
folder,
|
||||
sortOrder.name);
|
||||
|
@ -333,7 +332,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
|
||||
@Override
|
||||
public FileSortOrder getSortOrderByType(FileSortOrder.Type type, FileSortOrder defaultOrder) {
|
||||
User user = currentAccountProvider.getUser();
|
||||
User user = userAccountManager.getUser();
|
||||
if (user.isAnonymous()) {
|
||||
return defaultOrder;
|
||||
}
|
||||
|
@ -347,7 +346,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
|
||||
@Override
|
||||
public void setSortOrder(FileSortOrder.Type type, FileSortOrder sortOrder) {
|
||||
User user = currentAccountProvider.getUser();
|
||||
User user = userAccountManager.getUser();
|
||||
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
|
||||
dataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREF__FOLDER_SORT_ORDER + "_" + type, sortOrder.name);
|
||||
}
|
||||
|
@ -506,19 +505,19 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
@Override
|
||||
public void removeLegacyPreferences() {
|
||||
preferences.edit()
|
||||
.remove("instant_uploading")
|
||||
.remove("instant_video_uploading")
|
||||
.remove("instant_upload_path")
|
||||
.remove("instant_upload_path_use_subfolders")
|
||||
.remove("instant_upload_on_wifi")
|
||||
.remove("instant_upload_on_charging")
|
||||
.remove("instant_video_upload_path")
|
||||
.remove("instant_video_upload_path_use_subfolders")
|
||||
.remove("instant_video_upload_on_wifi")
|
||||
.remove("instant_video_uploading")
|
||||
.remove("instant_video_upload_on_charging")
|
||||
.remove("prefs_instant_behaviour")
|
||||
.apply();
|
||||
.remove("instant_uploading")
|
||||
.remove("instant_video_uploading")
|
||||
.remove("instant_upload_path")
|
||||
.remove("instant_upload_path_use_subfolders")
|
||||
.remove("instant_upload_on_wifi")
|
||||
.remove("instant_upload_on_charging")
|
||||
.remove("instant_video_upload_path")
|
||||
.remove("instant_video_upload_path_use_subfolders")
|
||||
.remove("instant_video_upload_on_wifi")
|
||||
.remove("instant_video_uploading")
|
||||
.remove("instant_video_upload_on_charging")
|
||||
.remove("prefs_instant_behaviour")
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -588,13 +587,12 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get preference value for a folder.
|
||||
* If folder is not set itself, it finds an ancestor that is set.
|
||||
* Get preference value for a folder. 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 folder Folder.
|
||||
* @param defaultValue Fallback value in case no ancestor is set.
|
||||
* @param folder Folder.
|
||||
* @param defaultValue Fallback value in case no ancestor is set.
|
||||
* @return Preference value
|
||||
*/
|
||||
private static String getFolderPreference(final Context context,
|
||||
|
@ -621,10 +619,10 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
/**
|
||||
* Set preference value for a folder.
|
||||
*
|
||||
* @param context Context object.
|
||||
* @param context Context object.
|
||||
* @param preferenceName Name of the preference to set.
|
||||
* @param folder Folder.
|
||||
* @param value Preference value to set.
|
||||
* @param folder Folder.
|
||||
* @param value Preference value to set.
|
||||
*/
|
||||
private static void setFolderPreference(final Context context,
|
||||
final User user,
|
||||
|
@ -637,7 +635,7 @@ public final class AppPreferencesImpl implements AppPreferences {
|
|||
|
||||
private static String getKeyFromFolder(String preferenceName, @Nullable OCFile folder) {
|
||||
final String folderIdString = String.valueOf(folder != null ? folder.getFileId() :
|
||||
FileDataStorageManager.ROOT_PARENT_ID);
|
||||
FileDataStorageManager.ROOT_PARENT_ID);
|
||||
|
||||
return preferenceName + "_" + folderIdString;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.nextcloud.client.preferences;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
|
@ -23,7 +23,7 @@ public class PreferencesModule {
|
|||
@Singleton
|
||||
public AppPreferences appPreferences(Context context,
|
||||
SharedPreferences sharedPreferences,
|
||||
CurrentAccountProvider currentAccountProvider) {
|
||||
return new AppPreferencesImpl(context, sharedPreferences, currentAccountProvider);
|
||||
UserAccountManager userAccountManager) {
|
||||
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,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils
|
||||
)
|
||||
|
|
|
@ -81,6 +81,24 @@ public class EmptyRecyclerView extends RecyclerView {
|
|||
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
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
super.onItemRangeInserted(positionStart, itemCount);
|
||||
|
|
|
@ -769,9 +769,7 @@ public abstract class DrawerActivity extends ToolbarActivity
|
|||
this,
|
||||
firstQuota.getIconUrl(),
|
||||
target,
|
||||
R.drawable.ic_link,
|
||||
size,
|
||||
size);
|
||||
R.drawable.ic_link);
|
||||
|
||||
} else {
|
||||
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)) {
|
||||
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);
|
||||
|
||||
for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
|
||||
|
@ -911,9 +907,7 @@ public abstract class DrawerActivity extends ToolbarActivity
|
|||
this,
|
||||
link.getIconUrl(),
|
||||
target,
|
||||
R.drawable.ic_link,
|
||||
size,
|
||||
size);
|
||||
R.drawable.ic_link);
|
||||
}
|
||||
|
||||
setDrawerMenuItemChecked(mCheckedMenuItem);
|
||||
|
|
|
@ -155,6 +155,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
|||
this,
|
||||
multipleAccountsSupported,
|
||||
true,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
|
||||
|
@ -310,6 +311,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
|||
this,
|
||||
multipleAccountsSupported,
|
||||
false,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
recyclerView.setAdapter(userListAdapter);
|
||||
|
@ -364,6 +366,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
|
|||
this,
|
||||
multipleAccountsSupported,
|
||||
false,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
recyclerView.setAdapter(userListAdapter);
|
||||
|
|
|
@ -62,6 +62,7 @@ import android.widget.Toast;
|
|||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.di.Injectable;
|
||||
import com.nextcloud.client.preferences.AppPreferences;
|
||||
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.ui.adapter.UploaderAdapter;
|
||||
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.CreateFolderDialogFragment;
|
||||
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.widget.SearchView;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
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.
|
||||
*/
|
||||
public class ReceiveExternalFilesActivity extends FileActivity
|
||||
implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
|
||||
SortingOrderDialogFragment.OnSortingOrderListener, Injectable {
|
||||
implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
|
||||
SortingOrderDialogFragment.OnSortingOrderListener, Injectable, AccountChooserInterface {
|
||||
|
||||
private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
|
||||
|
||||
|
@ -237,8 +238,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
|
|||
return this;
|
||||
}
|
||||
|
||||
public void changeAccount(Account account) {
|
||||
setAccount(account, false);
|
||||
@Override
|
||||
public void onAccountChosen(@NonNull User user) {
|
||||
setAccount(user.toPlatformAccount(), false);
|
||||
initTargetFolder();
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.PictureDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -147,7 +148,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
|
|||
holder.binding.message.setText(notification.getMessage());
|
||||
|
||||
if (!TextUtils.isEmpty(notification.getIcon())) {
|
||||
downloadIcon(notification.getIcon(), holder.binding.icon);
|
||||
downloadIcon(notification.getIcon(), holder.binding.icon, notificationsActivity);
|
||||
}
|
||||
|
||||
int nightModeFlag =
|
||||
|
@ -360,12 +361,12 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
|
|||
}
|
||||
}
|
||||
|
||||
private void downloadIcon(String icon, ImageView itemViewType) {
|
||||
GenericRequestBuilder<Uri, InputStream, SVG, PictureDrawable> requestBuilder = Glide.with(notificationsActivity)
|
||||
private void downloadIcon(String icon, ImageView itemViewType, Context context) {
|
||||
GenericRequestBuilder<Uri, InputStream, SVG, Drawable> requestBuilder = Glide.with(notificationsActivity)
|
||||
.using(Glide.buildStreamModelLoader(Uri.class, notificationsActivity), InputStream.class)
|
||||
.from(Uri.class)
|
||||
.as(SVG.class)
|
||||
.transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
|
||||
.transcode(new SvgDrawableTranscoder(context), Drawable.class)
|
||||
.sourceEncoder(new StreamEncoder())
|
||||
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder()))
|
||||
.decoder(new SvgDecoder())
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
package com.owncloud.android.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -40,7 +41,6 @@ import com.owncloud.android.databinding.AccountActionBinding;
|
|||
import com.owncloud.android.databinding.AccountItemBinding;
|
||||
import com.owncloud.android.lib.common.OwnCloudAccount;
|
||||
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.theme.ThemeColorUtils;
|
||||
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 final float accountAvatarRadiusDimension;
|
||||
private final BaseActivity context;
|
||||
private final Context context;
|
||||
private List<UserListItem> values;
|
||||
private Listener accountListAdapterListener;
|
||||
private final UserAccountManager accountManager;
|
||||
|
@ -69,15 +69,17 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
private final ClickListener clickListener;
|
||||
private final boolean showAddAccount;
|
||||
private final boolean showDotsMenu;
|
||||
private boolean highlightCurrentlyActiveAccount;
|
||||
private final ThemeColorUtils themeColorUtils;
|
||||
private final ThemeDrawableUtils themeDrawableUtils;
|
||||
|
||||
public UserListAdapter(BaseActivity context,
|
||||
public UserListAdapter(Context context,
|
||||
UserAccountManager accountManager,
|
||||
List<UserListItem> values,
|
||||
ClickListener clickListener,
|
||||
boolean showAddAccount,
|
||||
boolean showDotsMenu,
|
||||
boolean highlightCurrentlyActiveAccount,
|
||||
ThemeColorUtils themeColorUtils,
|
||||
ThemeDrawableUtils themeDrawableUtils) {
|
||||
this.context = context;
|
||||
|
@ -92,6 +94,7 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
this.showDotsMenu = showDotsMenu;
|
||||
this.themeColorUtils = themeColorUtils;
|
||||
this.themeDrawableUtils = themeDrawableUtils;
|
||||
this.highlightCurrentlyActiveAccount = highlightCurrentlyActiveAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -125,7 +128,7 @@ public class UserListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
if (UserListItem.TYPE_ACCOUNT == userListItem.getType()) {
|
||||
final User user = userListItem.getUser();
|
||||
AccountViewHolderItem item = (AccountViewHolderItem) holder;
|
||||
item.bind(user, userListItem.isEnabled(), this);
|
||||
item.bind(user, userListItem.isEnabled(), highlightCurrentlyActiveAccount, this);
|
||||
} // create add account action item
|
||||
else if (UserListItem.TYPE_ACTION_ADD == userListItem.getType() && accountListAdapterListener != null) {
|
||||
((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);
|
||||
setUser(user);
|
||||
setUsername(user);
|
||||
setAvatar(user, avatarGenerationListener);
|
||||
setCurrentlyActiveState(user);
|
||||
if (highlightCurrentlyActiveAccount) {
|
||||
setCurrentlyActiveState(user);
|
||||
} else {
|
||||
binding.ticker.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
if (!userListItemEnabled) {
|
||||
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.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -39,7 +40,6 @@ import com.nextcloud.client.account.UserAccountManager;
|
|||
import com.nextcloud.client.di.Injectable;
|
||||
import com.owncloud.android.R;
|
||||
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.UserListItem;
|
||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
||||
|
@ -60,6 +60,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
|||
@Inject UserAccountManager accountManager;
|
||||
@Inject ThemeColorUtils themeColorUtils;
|
||||
@Inject ThemeDrawableUtils themeDrawableUtils;
|
||||
public boolean highlightCurrentlyActiveAccount = true;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
|
@ -73,7 +74,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
|||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
MultipleAccountsBinding binding = MultipleAccountsBinding.inflate(inflater, null, false);
|
||||
|
||||
final ReceiveExternalFilesActivity parent = (ReceiveExternalFilesActivity) getActivity();
|
||||
final Context parent = getActivity();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
||||
|
||||
UserListAdapter adapter = new UserListAdapter(parent,
|
||||
|
@ -81,6 +82,7 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
|||
getAccountListItems(),
|
||||
this,
|
||||
false,
|
||||
highlightCurrentlyActiveAccount,
|
||||
false,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
|
@ -125,9 +127,9 @@ public class MultipleAccountsDialog extends DialogFragment implements Injectable
|
|||
|
||||
@Override
|
||||
public void onAccountClicked(User user) {
|
||||
final ReceiveExternalFilesActivity parentActivity = (ReceiveExternalFilesActivity) getActivity();
|
||||
final AccountChooserInterface parentActivity = (AccountChooserInterface) getActivity();
|
||||
if (parentActivity != null) {
|
||||
parentActivity.changeAccount(user.toPlatformAccount());
|
||||
parentActivity.onAccountChosen(user);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
|
|
@ -282,9 +282,7 @@ class BackupListAdapter(
|
|||
context,
|
||||
url,
|
||||
target,
|
||||
R.drawable.ic_user,
|
||||
imageView.width,
|
||||
imageView.height
|
||||
R.drawable.ic_user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,10 @@ import android.graphics.Canvas;
|
|||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.ImageView;
|
||||
|
@ -47,6 +49,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
|
@ -429,7 +432,7 @@ public final class BitmapUtils {
|
|||
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);
|
||||
int width = DisplayUtils.convertDpToPixel(2 * avatarRadius, context);
|
||||
|
||||
|
@ -453,6 +456,42 @@ public final class BitmapUtils {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -36,7 +36,6 @@ import android.content.res.Resources;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.PictureDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
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_PATTERN = "MMMM";
|
||||
public static final String YEAR_PATTERN = "yyyy";
|
||||
public static final int SVG_SIZE = 512;
|
||||
|
||||
private static Map<String, String> mimeType2HumanReadable;
|
||||
|
||||
|
@ -552,13 +552,10 @@ public final class DisplayUtils {
|
|||
Context context,
|
||||
String iconUrl,
|
||||
SimpleTarget imageView,
|
||||
int placeholder,
|
||||
int width,
|
||||
int height) {
|
||||
int placeholder) {
|
||||
try {
|
||||
if (iconUrl.endsWith(".svg")) {
|
||||
downloadSVGIcon(currentAccountProvider, clientFactory, context, iconUrl, imageView, placeholder, width,
|
||||
height);
|
||||
if (Uri.parse(iconUrl).getEncodedPath().endsWith(".svg")) {
|
||||
downloadSVGIcon(currentAccountProvider, clientFactory, context, iconUrl, imageView, placeholder);
|
||||
} else {
|
||||
downloadPNGIcon(context, iconUrl, imageView, placeholder);
|
||||
}
|
||||
|
@ -583,17 +580,15 @@ public final class DisplayUtils {
|
|||
Context context,
|
||||
String iconUrl,
|
||||
SimpleTarget imageView,
|
||||
int placeholder,
|
||||
int width,
|
||||
int height) {
|
||||
GenericRequestBuilder<Uri, InputStream, SVG, PictureDrawable> requestBuilder = Glide.with(context)
|
||||
int placeholder) {
|
||||
GenericRequestBuilder<Uri, InputStream, SVG, Drawable> requestBuilder = Glide.with(context)
|
||||
.using(new CustomGlideUriLoader(currentAccountProvider.getUser(), clientFactory), InputStream.class)
|
||||
.from(Uri.class)
|
||||
.as(SVG.class)
|
||||
.transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
|
||||
.transcode(new SvgDrawableTranscoder(context), Drawable.class)
|
||||
.sourceEncoder(new StreamEncoder())
|
||||
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder(height, width)))
|
||||
.decoder(new SvgDecoder(height, width))
|
||||
.cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder()))
|
||||
.decoder(new SvgDecoder())
|
||||
.placeholder(placeholder)
|
||||
.error(placeholder)
|
||||
.animate(android.R.anim.fade_in);
|
||||
|
|
|
@ -25,14 +25,6 @@ import java.io.InputStream;
|
|||
* Decodes an SVG internal representation from an {@link InputStream}.
|
||||
*/
|
||||
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() {
|
||||
// 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 {
|
||||
try {
|
||||
SVG svg = SVG.getFromInputStream(source);
|
||||
|
||||
if (width > 0) {
|
||||
svg.setDocumentWidth(width);
|
||||
}
|
||||
if (height > 0) {
|
||||
svg.setDocumentHeight(height);
|
||||
}
|
||||
svg.setDocumentViewBox(0, 0, svg.getDocumentWidth(), svg.getDocumentHeight());
|
||||
svg.setDocumentWidth("100%");
|
||||
svg.setDocumentHeight("100%");
|
||||
svg.setDocumentPreserveAspectRatio(PreserveAspectRatio.LETTERBOX);
|
||||
|
||||
return new SimpleResource<>(svg);
|
||||
|
|
|
@ -10,7 +10,12 @@
|
|||
*/
|
||||
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.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.PictureDrawable;
|
||||
|
||||
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}).
|
||||
*/
|
||||
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
|
||||
public Resource<PictureDrawable> transcode(Resource<SVG> toTranscode) {
|
||||
public Resource<Drawable> transcode(Resource<SVG> toTranscode) {
|
||||
SVG svg = toTranscode.get();
|
||||
Picture picture = svg.renderToPicture();
|
||||
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
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
package com.owncloud.android.utils.svg;
|
||||
|
||||
import android.graphics.drawable.PictureDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
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.Target;
|
||||
|
||||
public class SvgSoftwareLayerSetter<T> implements RequestListener<T, PictureDrawable> {
|
||||
public class SvgSoftwareLayerSetter<T> implements RequestListener<T, Drawable> {
|
||||
|
||||
@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();
|
||||
if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
|
||||
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
|
||||
|
@ -30,7 +30,7 @@ public class SvgSoftwareLayerSetter<T> implements RequestListener<T, PictureDraw
|
|||
}
|
||||
|
||||
@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) {
|
||||
ImageView view = ((ImageViewTarget<?>) target).getView();
|
||||
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"?>
|
||||
<resources>
|
||||
<string-array name="pref_behaviour_entries" translatable="false">
|
||||
<item>@string/pref_behaviour_entries_keep_file</item>
|
||||
<item>@string/pref_behaviour_entries_move</item>
|
||||
<item>@string/pref_behaviour_entries_delete_file</item>
|
||||
</string-array>
|
||||
<string-array name="pref_behaviour_entries" translatable="false">
|
||||
<item>@string/pref_behaviour_entries_keep_file</item>
|
||||
<item>@string/pref_behaviour_entries_move</item>
|
||||
<item>@string/pref_behaviour_entries_delete_file</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_behaviour_entryValues" translatable="false">
|
||||
<item>LOCAL_BEHAVIOUR_FORGET</item>
|
||||
<item>LOCAL_BEHAVIOUR_MOVE</item>
|
||||
<item>LOCAL_BEHAVIOUR_DELETE</item>
|
||||
</string-array>
|
||||
<string-array name="pref_behaviour_entryValues" translatable="false">
|
||||
<item>LOCAL_BEHAVIOUR_FORGET</item>
|
||||
<item>LOCAL_BEHAVIOUR_MOVE</item>
|
||||
<item>LOCAL_BEHAVIOUR_DELETE</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_name_collision_policy_entries" translatable="false">
|
||||
<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_editing</item>
|
||||
</string-array>
|
||||
<declare-styleable name="AppWidgetAttrs">
|
||||
<attr name="appWidgetPadding" format="dimension" />
|
||||
<attr name="appWidgetInnerRadius" format="dimension" />
|
||||
<attr name="appWidgetRadius" format="dimension" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Nextcloud Android client application
|
||||
|
||||
Copyright (C) 2012 Bartek Przybylski
|
||||
|
|
|
@ -955,8 +955,8 @@
|
|||
<string name="dnd">Do not disturb</string>
|
||||
<string name="away">Away</string>
|
||||
<string name="invisible">Invisible</string>
|
||||
<string translatable="false" name="divider">—</string>
|
||||
<string translatable="false" name="default_emoji">😃</string>
|
||||
<string name="divider" translatable="false">—</string>
|
||||
<string name="default_emoji" translatable="false">😃</string>
|
||||
<string name="dontClear">Don\'t clear</string>
|
||||
<string name="today">Today</string>
|
||||
<string name="thirtyMinutes">30 minutes</string>
|
||||
|
@ -1041,4 +1041,15 @@
|
|||
<string name="file_already_exists">Filename already exists</string>
|
||||
<string name="filedetails_export">Export</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>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
ownCloud Android client application
|
||||
|
||||
Copyright (C) 2012 Bartek Przybylski
|
||||
|
@ -101,8 +100,7 @@
|
|||
<item name="android:buttonBarButtonStyle">@style/FallbackTheming.Dialog.ButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FallbackTheming.Dialog.ButtonStyle"
|
||||
parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||
<style name="FallbackTheming.Dialog.ButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||
<item name="android:textColor">@color/text_color</item>
|
||||
</style>
|
||||
|
||||
|
@ -283,8 +281,7 @@
|
|||
<item name="colorAccent">@color/color_accent</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.ownCloud.Widget.ActionBar"
|
||||
parent="@style/Theme.MaterialComponents.Light.DarkActionBar.Bridge">
|
||||
<style name="Theme.ownCloud.Widget.ActionBar" parent="@style/Theme.MaterialComponents.Light.DarkActionBar.Bridge">
|
||||
<item name="android:background">@color/primary</item>
|
||||
<item name="background">@color/primary</item>
|
||||
<item name="android:textColor">@color/text_color</item>
|
||||
|
@ -339,8 +336,8 @@
|
|||
<item name="android:textSize">26sp</item>
|
||||
<item name="android:textColor">@color/text_color</item>
|
||||
</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">
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.App.Login.TextInputLayout</item>
|
||||
|
@ -447,4 +444,8 @@
|
|||
<item name="android:background">@drawable/ripple</item>
|
||||
</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>
|
||||
|
|
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.SharedPreferences;
|
||||
|
||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -128,7 +128,7 @@ public class TestAppPreferences {
|
|||
private SharedPreferences.Editor editor;
|
||||
|
||||
@Mock
|
||||
private CurrentAccountProvider accountProvider;
|
||||
private UserAccountManager userAccountManager;
|
||||
|
||||
private AppPreferencesImpl appPreferences;
|
||||
|
||||
|
@ -137,7 +137,7 @@ public class TestAppPreferences {
|
|||
MockitoAnnotations.initMocks(this);
|
||||
when(editor.remove(anyString())).thenReturn(editor);
|
||||
when(sharedPreferences.edit()).thenReturn(editor);
|
||||
appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, accountProvider);
|
||||
appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, userAccountManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -73,6 +73,7 @@ public class UserListAdapterTest {
|
|||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
assertEquals(0, userListAdapter.getItemCount());
|
||||
|
@ -93,6 +94,7 @@ public class UserListAdapterTest {
|
|||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
themeColorUtils,
|
||||
themeDrawableUtils);
|
||||
|
||||
|
@ -115,6 +117,7 @@ public class UserListAdapterTest {
|
|||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
themeColorUtils,
|
||||
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