diff options
139 files changed, 1637 insertions, 849 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 008a5554f..af5f83305 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <application - android:name="it.niedermann.nextcloud.deck.Application" + android:name="it.niedermann.nextcloud.deck.DeckApplication" android:allowBackup="true" android:fullBackupContent="false" android:hardwareAccelerated="true" @@ -71,20 +71,17 @@ <activity android:name=".ui.archivedcards.ArchivedCardsActvitiy" android:label="@string/archived_cards" - android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" - android:theme="@style/AppTheme" /> + android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" /> <activity android:name=".ui.archivedboards.ArchivedBoardsActvitiy" android:label="@string/archived_boards" - android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" - android:theme="@style/AppTheme" /> + android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" /> <activity android:name=".ui.card.EditActivity" android:label="@string/edit" - android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" - android:theme="@style/AppTheme" /> + android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" /> <activity android:name=".ui.attachments.AttachmentsActivity" @@ -95,19 +92,16 @@ <activity android:name=".ui.settings.SettingsActivity" android:label="@string/simple_settings" - android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" - android:theme="@style/AppTheme" /> + android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" /> <activity android:name=".ui.ImportAccountActivity" - android:label="@string/app_name" - android:theme="@style/AppTheme" /> + android:label="@string/app_name" /> <activity android:name=".ui.preparecreate.PrepareCreateActivity" android:description="@string/add_a_new_card_using_the_button" - android:label="@string/add_card" - android:theme="@style/AppTheme"> + android:label="@string/add_card" > <intent-filter> <action android:name="android.intent.action.SEND" /> @@ -119,13 +113,11 @@ <activity android:name=".ui.about.AboutActivity" android:label="@string/about" - android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" - android:theme="@style/AppTheme" /> + android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" /> <activity android:name=".ui.PushNotificationActivity" - android:label="@string/app_name" - android:theme="@style/AppTheme"> + android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter> diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/Application.java b/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java index 6fbf7c8a2..ff492c226 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/Application.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java @@ -1,23 +1,20 @@ package it.niedermann.nextcloud.deck; +import android.app.Application; import android.content.Context; import android.content.SharedPreferences; -import android.graphics.Color; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import com.jakewharton.threetenabp.AndroidThreeTen; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; - import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode; import static androidx.multidex.MultiDex.install; -public class Application extends android.app.Application { +public class DeckApplication extends Application { public static final long NO_ACCOUNT_ID = -1L; public static final long NO_BOARD_ID = -1L; @@ -25,7 +22,7 @@ public class Application extends android.app.Application { @Override public void onCreate() { - setAppTheme(getAppTheme(getApplicationContext())); + setAppTheme(isDarkTheme(getApplicationContext())); super.onCreate(); AndroidThreeTen.init(this); } @@ -48,56 +45,11 @@ public class Application extends android.app.Application { setDefaultNightMode(darkTheme ? MODE_NIGHT_YES : MODE_NIGHT_NO); } - public static boolean getAppTheme(@NonNull Context context) { + public static boolean isDarkTheme(@NonNull Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getBoolean(context.getString(R.string.pref_key_dark_theme), false); } - // -------- - // Branding - // -------- - - public static boolean isBrandingEnabled(@NonNull Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return prefs.getBoolean(context.getString(R.string.pref_key_branding), true); - } - - @ColorInt - public static int readBrandMainColor(@NonNull Context context) { - if (Application.isBrandingEnabled(context)) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); - DeckLog.log("--- Read: shared_preference_theme_main"); - return sharedPreferences.getInt(context.getString(R.string.shared_preference_theme_main), context.getApplicationContext().getResources().getColor(R.color.primary)); - } else { - return context.getResources().getColor(R.color.primary); - } - } - - @ColorInt - public static int readBrandTextColor(@NonNull Context context) { - if (isBrandingEnabled(context)) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); - DeckLog.log("--- Read: shared_preference_theme_text"); - return sharedPreferences.getInt(context.getString(R.string.shared_preference_theme_text), context.getApplicationContext().getResources().getColor(android.R.color.white)); - } else { - return Color.WHITE; - } - } - - public static void saveBrandColors(@NonNull Context context, @ColorInt int mainColor, @ColorInt int textColor) { - if (isBrandingEnabled(context) && context instanceof BrandedActivity) { - final BrandedActivity activity = (BrandedActivity) context; - activity.applyBrand(mainColor, textColor); - BrandedActivity.applyBrandToStatusbar(activity.getWindow(), mainColor, textColor); - } - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); - DeckLog.log("--- Write: shared_preference_theme_main" + " | " + mainColor); - DeckLog.log("--- Write: shared_preference_theme_text" + " | " + textColor); - editor.putInt(context.getString(R.string.shared_preference_theme_main), mainColor); - editor.putInt(context.getString(R.string.shared_preference_theme_text), textColor); - editor.apply(); - } - // -------------------------------------- // Current account / board / stack states // -------------------------------------- diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java index f81c9f191..29b3706fa 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java @@ -16,6 +16,8 @@ import it.niedermann.nextcloud.deck.model.full.FullStack; import it.niedermann.nextcloud.deck.model.ocs.Activity; import it.niedermann.nextcloud.deck.model.ocs.Capabilities; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList; /** * Created by david on 27.06.17. @@ -37,9 +39,9 @@ public class GsonConfig { Type stackList = new TypeToken<List<FullStack>>() {}.getType(); Type stack = new TypeToken<FullStack>() {}.getType(); Type capabilities = new TypeToken<Capabilities>() {}.getType(); - Type capabilitiesList = new TypeToken<List<Capabilities>>() {}.getType(); + Type ocsUserList = new TypeToken<OcsUserList>() {}.getType(); + Type ocsUser = new TypeToken<OcsUser>() {}.getType(); Type activity = new TypeToken<Activity>() {}.getType(); - Type activityList = new TypeToken<List<Activity>>() {}.getType(); Type attachment = new TypeToken<Attachment>() {}.getType(); Type attachmentList = new TypeToken<List<Attachment>>() {}.getType(); Type comment = new TypeToken<OcsComment>() {}.getType(); @@ -56,9 +58,9 @@ public class GsonConfig { .registerTypeAdapter(label, new NextcloudDeserializer<>("label", Label.class)) .registerTypeAdapter(stackList, new NextcloudArrayDeserializer<>("stacks", FullStack.class)) .registerTypeAdapter(stack, new NextcloudDeserializer<>("stack", FullStack.class)) - .registerTypeAdapter(capabilitiesList, new NextcloudArrayDeserializer<>("capabilities", Capabilities.class)) .registerTypeAdapter(capabilities, new NextcloudDeserializer<>("capability", Capabilities.class)) - .registerTypeAdapter(activityList, new NextcloudDeserializer<>("activities", Activity.class)) + .registerTypeAdapter(ocsUserList, new NextcloudDeserializer<>("ocsUserList", OcsUserList.class)) + .registerTypeAdapter(ocsUser, new NextcloudDeserializer<>("ocsUser", OcsUser.class)) .registerTypeAdapter(activity, new NextcloudDeserializer<>("activity", Activity.class)) .registerTypeAdapter(attachmentList, new NextcloudArrayDeserializer<>("attachments", Attachment.class)) .registerTypeAdapter(attachment, new NextcloudDeserializer<>("attachment", Attachment.class)) diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java index c64e5db1d..6dedefa01 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java @@ -32,6 +32,8 @@ import it.niedermann.nextcloud.deck.model.ocs.Version; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.Mention; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList; import static it.niedermann.nextcloud.deck.exceptions.DeckException.Hint.CAPABILITIES_VERSION_NOT_PARSABLE; @@ -50,6 +52,10 @@ public class JsonToEntityParser { return (T) parseActivity(obj); } else if (mType == Capabilities.class) { return (T) parseCapabilities(obj); + } else if (mType == OcsUserList.class) { + return (T) parseOcsUserList(obj); + } else if (mType == OcsUser.class) { + return (T) parseOcsUser(obj); } else if (mType == Attachment.class) { return (T) parseAttachment(obj); } else if (mType == OcsComment.class) { @@ -58,6 +64,43 @@ public class JsonToEntityParser { throw new IllegalArgumentException("unregistered type: " + mType.getCanonicalName()); } + private static OcsUser parseOcsUser(JsonObject obj) { + DeckLog.verbose(obj.toString()); + OcsUser ocsUser = new OcsUser(); + TraceableException.makeTraceableIfFails(() -> { + JsonElement data = obj.get("ocs").getAsJsonObject().get("data"); + if (!data.isJsonNull()) { + JsonObject jsonObject = data.getAsJsonObject(); + if (jsonObject.has("id")) { + ocsUser.setId(getNullAsEmptyString(jsonObject.get("id"))); + } + if (jsonObject.has("displayname")) { + ocsUser.setDisplayName(getNullAsEmptyString(jsonObject.get("displayname"))); + } + } + + }, obj); + return ocsUser; + } + + private static OcsUserList parseOcsUserList(JsonObject obj) { + DeckLog.verbose(obj.toString()); + OcsUserList ocsUserList = new OcsUserList(); + TraceableException.makeTraceableIfFails(() -> { + JsonElement data = obj.get("ocs").getAsJsonObject().get("data"); + if (!data.isJsonNull() && data.getAsJsonObject().has("users")) { + JsonElement users = data.getAsJsonObject().get("users"); + if (!users.isJsonNull() && users.isJsonArray()) { + for (JsonElement userElement : users.getAsJsonArray()) { + ocsUserList.add(userElement.getAsString()); + } + } + } + + }, obj); + return ocsUserList; + } + private static OcsComment parseOcsComment(JsonObject obj) { DeckLog.verbose(obj.toString()); OcsComment comment = new OcsComment(); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java index b3e4f4c84..d69ee8513 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java @@ -8,6 +8,8 @@ import it.niedermann.nextcloud.deck.model.ocs.Activity; import it.niedermann.nextcloud.deck.model.ocs.Capabilities; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.GET; @@ -22,6 +24,12 @@ public interface NextcloudServerAPI { @GET("cloud/capabilities?format=json") Observable<Capabilities> getCapabilities(); + @GET("cloud/users?format=json") + Observable<OcsUserList> getAllUsers(); + + @GET("cloud/users/{uid}?format=json") + Observable<OcsUser> getUserDetails(@Path("uid") String uid); + @GET("apps/activity/api/v2/activity/filter?format=json&object_type=deck_card&limit=50&since=-1&sort=asc") Observable<List<Activity>> getActivitiesForCard(@Query("object_id") long cardId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/RequestHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/RequestHelper.java index 44ba28802..6bdfbbee1 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/api/RequestHelper.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/RequestHelper.java @@ -11,9 +11,7 @@ import it.niedermann.nextcloud.deck.DeckLog; public class RequestHelper { static { - RxJavaPlugins.setErrorHandler(e -> { - DeckLog.logError(e); - }); + RxJavaPlugins.setErrorHandler(DeckLog::logError); } public static <T> void request(final ApiProvider provider, final ObservableProvider<T> call, final IResponseCallback<T> callback) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUser.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUser.java new file mode 100644 index 000000000..24627cb9a --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUser.java @@ -0,0 +1,49 @@ +package it.niedermann.nextcloud.deck.model.ocs.user; + +public class OcsUser { + String id; + String displayName; + + public OcsUser() { + + } + + public OcsUser(String id, String displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OcsUser ocsUser = (OcsUser) o; + + if (id != null ? !id.equals(ocsUser.id) : ocsUser.id != null) return false; + return displayName != null ? displayName.equals(ocsUser.displayName) : ocsUser.displayName == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (displayName != null ? displayName.hashCode() : 0); + return result; + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java new file mode 100644 index 000000000..a10ae005d --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java @@ -0,0 +1,7 @@ +package it.niedermann.nextcloud.deck.model.ocs.user; + +import java.util.ArrayList; + +public class OcsUserList extends ArrayList<String> { + // nothing. +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java index 1cb7112e7..9ed9035cf 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java @@ -3,11 +3,13 @@ package it.niedermann.nextcloud.deck.persistence.sync; import android.content.Context; import android.database.sqlite.SQLiteConstraintException; +import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.core.util.Pair; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MutableLiveData; import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; @@ -44,8 +46,11 @@ import it.niedermann.nextcloud.deck.model.ocs.Capabilities; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList; import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter; +import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData; import it.niedermann.nextcloud.deck.persistence.sync.helpers.DataPropagationHelper; import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper; @@ -59,6 +64,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.CardPropa import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.DeckCommentsDataProvider; import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.LabelDataProvider; import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.StackDataProvider; +import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWitAclDownSyncDataProvider; import it.niedermann.nextcloud.deck.util.DateUtil; import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; @@ -73,10 +79,12 @@ public class SyncManager { @NonNull private ServerAdapter serverAdapter; + @AnyThread public SyncManager(@NonNull Context context) { this(context, null); } + @AnyThread public SyncManager(@NonNull Context context, @Nullable String ssoAccountName) { appContext = context.getApplicationContext(); LastSyncUtil.init(appContext); @@ -84,11 +92,13 @@ public class SyncManager { this.serverAdapter = new ServerAdapter(appContext, ssoAccountName); } - private void doAsync(Runnable r) { + @AnyThread + private void doAsync(@NonNull Runnable r) { new Thread(r).start(); } - public MutableLiveData<FullCard> synchronizeCardByRemoteId(long cardRemoteId, Account account) { + @AnyThread + public MutableLiveData<FullCard> synchronizeCardByRemoteId(long cardRemoteId, @NonNull Account account) { MutableLiveData<FullCard> liveData = new MutableLiveData<>(); doAsync(() -> { Long accountId = account.getId(); @@ -114,10 +124,12 @@ public class SyncManager { } // TODO if the card does not exist yet, try to synchronize it first, instead of directly returning null. If sync failed, return null. - public LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccount(long cardRemoteId, Account account) { + @AnyThread + public LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccount(long cardRemoteId, @NonNull Account account) { return dataBaseAdapter.getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, account.getId()); } + @AnyThread public boolean synchronizeEverything() { List<Account> accounts = dataBaseAdapter.getAllAccountsDirectly(); if (accounts.size() > 0) { @@ -150,11 +162,13 @@ public class SyncManager { return true; } - public void synchronize(IResponseCallback<Boolean> responseCallback) { - if (responseCallback == null || - responseCallback.getAccount() == null || - responseCallback.getAccount().getId() == null) { - throw new IllegalArgumentException("please provide an account ID."); + @AnyThread + public void synchronize(@NonNull IResponseCallback<Boolean> responseCallback) { + if(responseCallback.getAccount() == null) { + throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must not be null."); + } + if(responseCallback.getAccount().getId() == null) { + throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must contain a valid id, but given id was null."); } doAsync(() -> refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) { @Override @@ -254,11 +268,13 @@ public class SyncManager { // return remoteEntity; // } + @AnyThread public LiveData<Boolean> hasAccounts() { return dataBaseAdapter.hasAccounts(); } - public WrappedLiveData<Account> createAccount(Account accout) { + @AnyThread + public WrappedLiveData<Account> createAccount(@NonNull Account accout) { return dataBaseAdapter.createAccount(accout); } @@ -277,19 +293,56 @@ public class SyncManager { dataBaseAdapter.updateAccount(account); } + @AnyThread public LiveData<Account> readAccount(long id) { return dataBaseAdapter.readAccount(id); } - public LiveData<Account> readAccount(String name) { + @AnyThread + public LiveData<Account> readAccount(@Nullable String name) { return dataBaseAdapter.readAccount(name); } + @AnyThread public LiveData<List<Account>> readAccounts() { return dataBaseAdapter.readAccounts(); } - public void refreshCapabilities(IResponseCallback<Capabilities> callback) { + /** + * <p> + * Since the return value is a {@link LiveData}, it should immediately return the available values from the database + * and then perform a synchronization (not full but only for the needed data) to update the return value. + * <p> + * See https://github.com/stefan-niedermann/nextcloud-deck/issues/498#issuecomment-631615680 + * + * @param host e. g. "example.com:4711" + * @return a {@link List<Account>} of {@link Account}s which are + * - located at the given {@param host} + * - and have the permission to read the board with the given {@param boardRemoteId} (aka the {@link Board} is shared with this {@link User}). + */ + @AnyThread + public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) { + MediatorLiveData<List<Account>> liveData = new MediatorLiveData<>(); + liveData.addSource(dataBaseAdapter.readAccountsForHostWithReadAccessToBoard(host, boardRemoteId), accounts -> { + liveData.postValue(accounts); + doAsync(() -> { + for (Account account : accounts) { + new SyncHelper(serverAdapter, dataBaseAdapter, null) + .setResponseCallback(new IResponseCallback<Boolean>(account) { + @Override + public void onResponse(Boolean response) { + liveData.postValue(dataBaseAdapter.readAccountsForHostWithReadAccessToBoardDirectly(host, boardRemoteId)); + } + }).doSyncFor(new BoardWitAclDownSyncDataProvider()); + } + }); + }); + + return liveData; + } + + @AnyThread + public void refreshCapabilities(@NonNull IResponseCallback<Capabilities> callback) { doAsync(() -> { try { serverAdapter.getCapabilities(new IResponseCallback<Capabilities>(callback.getAccount()) { @@ -324,6 +377,34 @@ public class SyncManager { } catch (OfflineException e) { callback.onError(e); } + + try { + serverAdapter.getAllOcsUsers(new IResponseCallback<OcsUserList>(callback.getAccount()) { + @Override + public void onResponse(OcsUserList response) { + Long accountId = callback.getAccount().getId(); + for (String ocsUserName : response) { + User existingUser = dataBaseAdapter.getUserByUidDirectly(accountId, ocsUserName); + if (existingUser == null) { + // we don't know this user, lets get some details... + serverAdapter.getOcsUserDetails(ocsUserName, new IResponseCallback<OcsUser>(callback.getAccount()) { + @Override + public void onResponse(OcsUser response) { + User newUser = new User(); + newUser.setStatus(DBStatus.UP_TO_DATE.getId()); + newUser.setPrimaryKey(ocsUserName); + newUser.setUid(ocsUserName); + newUser.setDisplayname(response.getDisplayName()); + dataBaseAdapter.createUser(accountId, newUser); + } + }); + } + } + } + }); + } catch (OfflineException ignored) { + // Nothing to do here... + } }); } @@ -332,6 +413,7 @@ public class SyncManager { * @param archived Decides whether only archived or not-archived boards for the specified account will be returned * @return all archived or non-archived <code>Board</code>s depending on <code>archived</code> parameter */ + @AnyThread public LiveData<List<Board>> getBoards(long accountId, boolean archived) { return dataBaseAdapter.getBoards(accountId, archived); } @@ -341,6 +423,7 @@ public class SyncManager { * @param archived Decides whether only archived or not-archived boards for the specified account will be returned * @return all archived or non-archived <code>FullBoard</code>s depending on <code>archived</code> parameter */ + @AnyThread public LiveData<List<FullBoard>> getFullBoards(long accountId, boolean archived) { return dataBaseAdapter.getFullBoards(accountId, archived); } @@ -351,15 +434,18 @@ public class SyncManager { * @param accountId ID of the account * @return all non-archived <code>Board</code>s with edit permission */ + @AnyThread public LiveData<List<Board>> getBoardsWithEditPermission(long accountId) { return dataBaseAdapter.getBoardsWithEditPermission(accountId); } + @AnyThread public LiveData<Boolean> hasArchivedBoards(long accountId) { return dataBaseAdapter.hasArchivedBoards(accountId); } - public LiveData<FullBoard> createBoard(long accountId, Board board) { + @AnyThread + public LiveData<FullBoard> createBoard(long accountId, @NonNull Board board) { MutableLiveData<FullBoard> liveData = new MutableLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); @@ -390,11 +476,62 @@ public class SyncManager { * Owner of the target {@link Board} will be the {@link User} with the {@link Account} of {@param targetAccountId}. * Does <strong>not</strong> clone any {@link Card} or {@link AccessControl} from the origin {@link Board}. */ - public LiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, String targetBoardTitle, String targetBoardColor) { - throw new UnsupportedOperationException("Not yet implemented"); + @AnyThread + public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, String targetBoardTitle, String targetBoardColor) { + WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>(); + + doAsync(() -> { + Account originAccount = dataBaseAdapter.getAccountByIdDirectly(originAccountId); + User newOwner = dataBaseAdapter.getUserByUidDirectly(originAccountId, originAccount.getUserName()); + FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(originAccountId, originBoardLocalId); + originalBoard.setAccountId(targetAccountId); + originalBoard.getBoard().setTitle(targetBoardTitle); + originalBoard.getBoard().setColor(targetBoardColor); + originalBoard.getBoard().setOwnerId(newOwner.getId()); + originalBoard.setStatusEnum(DBStatus.LOCAL_EDITED); + originalBoard.setOwner(newOwner); + originalBoard.setId(null); + originalBoard.setLocalId(null); + long newBoardId = dataBaseAdapter.createBoardDirectly(originAccountId, originalBoard.getBoard()); + originalBoard.setLocalId(newBoardId); + + for (Stack stack : originalBoard.getStacks()) { + stack.setLocalId(null); + stack.setId(null); + stack.setStatusEnum(DBStatus.LOCAL_EDITED); + stack.setAccountId(targetAccountId); + stack.setBoardId(newBoardId); + dataBaseAdapter.createStack(targetAccountId, stack); + } + for (Label label : originalBoard.getLabels()) { + label.setLocalId(null); + label.setId(null); + label.setAccountId(targetAccountId); + label.setStatusEnum(DBStatus.LOCAL_EDITED); + label.setBoardId(newBoardId); + dataBaseAdapter.createLabel(targetAccountId, label); + } + Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId); + new SyncHelper(serverAdapter, dataBaseAdapter, null) + .setResponseCallback(new IResponseCallback<Boolean>(targetAccount) { + @Override + public void onResponse(Boolean response) { + liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId)); + } + + @Override + public void onError(Throwable throwable) { + super.onError(throwable); + liveData.postError(throwable); + } + }).doSyncFor(new BoardDataProvider()); + + }); + return liveData; } - public LiveData<List<it.niedermann.nextcloud.deck.model.ocs.Activity>> syncActivitiesForCard(Card card) { + @AnyThread + public LiveData<List<it.niedermann.nextcloud.deck.model.ocs.Activity>> syncActivitiesForCard(@NonNull Card card) { doAsync(() -> { if (serverAdapter.hasInternetConnection()) { if (card.getId() != null) { @@ -413,7 +550,8 @@ public class SyncManager { return dataBaseAdapter.getActivitiesForCard(card.getLocalId()); } - public void addCommentToCard(long accountId, long cardId, DeckComment comment) { + @AnyThread + public void addCommentToCard(long accountId, long cardId, @NonNull DeckComment comment) { doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); Card card = dataBaseAdapter.getCardByLocalIdDirectly(accountId, cardId); @@ -427,6 +565,7 @@ public class SyncManager { }); } + @AnyThread public void updateComment(long accountId, long localCardId, long localCommentId, String comment) { doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); @@ -443,6 +582,7 @@ public class SyncManager { }); } + @AnyThread public WrappedLiveData<Void> deleteComment(long accountId, long localCardId, long localCommentId) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -460,7 +600,8 @@ public class SyncManager { return dataBaseAdapter.getFullCommentsForLocalCardId(localCardId); } - public WrappedLiveData<Void> deleteBoard(Board board) { + @AnyThread + public WrappedLiveData<Void> deleteBoard(@NonNull Board board) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { long accountId = board.getAccountId(); @@ -471,7 +612,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<FullBoard> updateBoard(FullBoard board) { + @AnyThread + public WrappedLiveData<FullBoard> updateBoard(@NonNull FullBoard board) { WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>(); long accountId = board.getAccountId(); doAsync(() -> { @@ -499,6 +641,7 @@ public class SyncManager { return dataBaseAdapter.getStack(accountId, localStackId); } + @AnyThread public WrappedLiveData<AccessControl> createAccessControl(long accountId, AccessControl entity) { WrappedLiveData<AccessControl> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -515,6 +658,7 @@ public class SyncManager { return liveData; } + @WorkerThread public AccessControl getAccessControlByRemoteIdDirectly(long accountId, Long id) { return dataBaseAdapter.getAccessControlByRemoteIdDirectly(accountId, id); } @@ -523,7 +667,8 @@ public class SyncManager { return dataBaseAdapter.getAccessControlByLocalBoardId(accountId, id); } - public WrappedLiveData<AccessControl> updateAccessControl(AccessControl entity) { + @AnyThread + public WrappedLiveData<AccessControl> updateAccessControl(@NonNull AccessControl entity) { WrappedLiveData<AccessControl> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(entity.getAccountId()); @@ -534,7 +679,8 @@ public class SyncManager { return liveData; } - private <T> IResponseCallback<T> getCallbackToLiveDataConverter(Account account, WrappedLiveData<T> liveData) { + @AnyThread + private <T> IResponseCallback<T> getCallbackToLiveDataConverter(Account account, @NonNull WrappedLiveData<T> liveData) { return new IResponseCallback<T>(account) { @Override public void onResponse(T response) { @@ -548,7 +694,8 @@ public class SyncManager { }; } - public WrappedLiveData<Void> deleteAccessControl(AccessControl entity) { + @AnyThread + public WrappedLiveData<Void> deleteAccessControl(@NonNull AccessControl entity) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(entity.getAccountId()); @@ -577,8 +724,8 @@ public class SyncManager { return dataBaseAdapter.getFullBoardById(accountId, localId); } - - public WrappedLiveData<FullStack> createStack(long accountId, Stack stack) { + @AnyThread + public WrappedLiveData<FullStack> createStack(long accountId, @NonNull Stack stack) { WrappedLiveData<FullStack> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); @@ -594,6 +741,7 @@ public class SyncManager { return liveData; } + @AnyThread public WrappedLiveData<Void> deleteStack(long accountId, long stackLocalId, long boardLocalId) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -605,7 +753,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<FullStack> updateStack(FullStack stack) { + @AnyThread + public WrappedLiveData<FullStack> updateStack(@NonNull FullStack stack) { WrappedLiveData<FullStack> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(stack.getAccountId()); @@ -616,6 +765,7 @@ public class SyncManager { } + @AnyThread private void updateStack(@NonNull Account account, @NonNull FullBoard board, @NonNull FullStack stack, @Nullable WrappedLiveData<FullStack> liveData) { doAsync(() -> { new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new StackDataProvider(null, board), stack, new IResponseCallback<FullStack>(account) { @@ -641,6 +791,7 @@ public class SyncManager { * * @param stackLocalIds The first item of the pair will be updated first */ + @AnyThread public void swapStackOrder(long accountId, long boardLocalId, @NonNull Pair<Long, Long> stackLocalIds) { if (stackLocalIds.first == null || stackLocalIds.second == null) { throw new IllegalArgumentException("Given stackLocalIds must not be null"); @@ -711,8 +862,8 @@ public class SyncManager { // return liveData; // } - public LiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, FullCard card) { - + @AnyThread + public LiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) { MutableLiveData<FullCard> liveData = new MutableLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); @@ -760,7 +911,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<Void> deleteCard(Card card) { + @AnyThread + public WrappedLiveData<Void> deleteCard(@NonNull Card card) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId()); @@ -775,7 +927,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<FullCard> archiveCard(FullCard card) { + @AnyThread + public WrappedLiveData<FullCard> archiveCard(@NonNull FullCard card) { WrappedLiveData<FullCard> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId()); @@ -787,11 +940,12 @@ public class SyncManager { return liveData; } - private void updateCardForArchive(Account account, FullStack stack, Board board, FullCard card, IResponseCallback<FullCard> callback) { + private void updateCardForArchive(Account account, FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) { new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new CardDataProvider(null, board, stack), card, callback); } - public WrappedLiveData<FullCard> dearchiveCard(FullCard card) { + @AnyThread + public WrappedLiveData<FullCard> dearchiveCard(@NonNull FullCard card) { WrappedLiveData<FullCard> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId()); @@ -803,6 +957,7 @@ public class SyncManager { return liveData; } + @AnyThread public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -840,7 +995,8 @@ public class SyncManager { return liveData; } - public void archiveBoard(Board board) { + @AnyThread + public void archiveBoard(@NonNull Board board) { doAsync(() -> { FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId()); b.getBoard().setArchived(true); @@ -848,7 +1004,8 @@ public class SyncManager { }); } - public void dearchiveBoard(Board board) { + @AnyThread + public void dearchiveBoard(@NonNull Board board) { doAsync(() -> { FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId()); b.getBoard().setArchived(false); @@ -856,7 +1013,8 @@ public class SyncManager { }); } - public WrappedLiveData<FullCard> updateCard(FullCard card) { + @AnyThread + public WrappedLiveData<FullCard> updateCard(@NonNull FullCard card) { WrappedLiveData<FullCard> liveData = new WrappedLiveData<>(); doAsync(() -> { FullCard fullCardFromDB = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId()); @@ -912,6 +1070,7 @@ public class SyncManager { /** * Moves the given {@param originCardLocalId} to the new target coordinates specified by {@param targetAccountId}, {@param targetBoardLocalId} and {@param targetStackLocalId}. * If the {@param targetBoardLocalId} changes, this will apply some logic to make sure that we migrate as much data as possible without the risk of getting an illegal state. + * Attachments are not copied or anything. * <p> * 1) {@link FullCard#labels} * <p> @@ -925,14 +1084,134 @@ public class SyncManager { * a) If the {@link User} has at least view permission at the target {@link Board}, keep it (<strong>can</strong> be the case if the target {@link Account} is the same as the origin {@link Account} <strong>or</strong> the target {@link Account} is on the same Nextcloud instance as the origin {@link Account} * b) Else {@link #unassignUserFromCard(User, Card)} (will always be the case if the target {@link Account} is on another Nextcloud isntance as the origin {@link Account}) * <p> - * + * <p> * https://github.com/stefan-niedermann/nextcloud-deck/issues/453 */ @SuppressWarnings("JavadocReference") + @AnyThread public WrappedLiveData<Void> moveCard(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) { - throw new UnsupportedOperationException("Not yet implemented"); + return LiveDataHelper.wrapInLiveData(() -> { + + FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId); + int newIndex = dataBaseAdapter.getHighestCardOrderInStack(targetStackLocalId) + 1; + FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId); + // ### maybe shortcut possible? (just moved to another stack) + if (targetBoardLocalId == originalBoard.getLocalId()) { + reorder(originAccountId, originalCard, targetStackLocalId, newIndex); + return null; + } + // ### get rid of original card where it is now. + Card originalInnerCard = originalCard.getCard(); + deleteCard(originalInnerCard); + // ### clone card itself + Card targetCard = originalInnerCard; + targetCard.setAccountId(targetAccountId); + targetCard.setId(null); + targetCard.setLocalId(null); + targetCard.setStatusEnum(DBStatus.LOCAL_EDITED); + targetCard.setStackId(targetStackLocalId); + targetCard.setOrder(newIndex); + //TODO: this needs to propagate to server as well, since anything else propagates as well (otherwise card isn't known on server) + FullCard fullCardForServerPropagation = new FullCard(); + fullCardForServerPropagation.setCard(targetCard); + + Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId); + FullBoard targetBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, targetBoardLocalId); + FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId); + User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName()); + CountDownLatch latch = new CountDownLatch(1); + new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) { + @Override + public void onResponse(FullCard response) { + targetCard.setId(response.getId()); + targetCard.setLocalId(response.getLocalId()); + latch.countDown(); + } + + @Override + public void onError(Throwable throwable) { + super.onError(throwable); + throw new RuntimeException("unable to create card in moveCard target", throwable); + } + }, (FullCard entity, FullCard response) -> { + response.getCard().setUserId(entity.getCard().getUserId()); + response.getCard().setStackId(targetFullStack.getLocalId()); + }); + + try { + latch.await(); + } catch (InterruptedException e) { + DeckLog.logError(e); + throw new RuntimeException("error fulfilling countDownLatch", e); + } + + long newCardId = targetCard.getLocalId(); + + // ### clone labels, assign them + // prepare + // has user of targetaccount manage permissions? + boolean hasManagePermission = targetBoard.getBoard().getOwnerId() == userOfTargetAccount.getLocalId(); + List<AccessControl> aclOfTargetBoard = dataBaseAdapter.getAccessControlByLocalBoardIdDirectly(targetAccountId, targetBoard.getLocalId()); + if (!hasManagePermission) { + for (AccessControl accessControl : aclOfTargetBoard) { + if (accessControl.getUserId() == userOfTargetAccount.getLocalId() && accessControl.isPermissionManage()) { + hasManagePermission = true; + break; + } + } + } + + // actual doing + for (Label originalLabel : originalCard.getLabels()) { + // already exists? + Label existingMatch = null; + for (Label targetBoardLabel : targetBoard.getLabels()) { + if (originalLabel.getTitle().trim().equalsIgnoreCase(targetBoardLabel.getTitle().trim())) { + existingMatch = targetBoardLabel; + break; + } + } + if (existingMatch == null) { + if (hasManagePermission) { + originalLabel.setBoardId(targetBoardLocalId); + originalLabel.setId(null); + originalLabel.setLocalId(null); + originalLabel.setStatusEnum(DBStatus.LOCAL_EDITED); + originalLabel.setAccountId(targetBoard.getAccountId()); + createAndAssignLabelToCard(originalBoard.getAccountId(), originalLabel, newCardId); + } + } else { + assignLabelToCard(existingMatch, targetCard); + } + } + + // ### Clone assigned users + Account originalAccount = dataBaseAdapter.getAccountByIdDirectly(originAccountId); + // same instance? otherwise doesn't make sense + if (originalAccount.getUrl().equalsIgnoreCase(targetAccount.getUrl())) { + for (User assignedUser : originalCard.getAssignedUsers()) { + // has assignedUser at least view permissions? + boolean hasViewPermission = targetBoard.getBoard().getOwnerId() == assignedUser.getLocalId(); + if (!hasViewPermission) { + for (AccessControl accessControl : aclOfTargetBoard) { + if (accessControl.getUserId() == userOfTargetAccount.getLocalId()) { + // ACL exists, so viewing is granted + hasViewPermission = true; + break; + } + } + } + if (hasViewPermission) { + assignUserToCard(assignedUser, targetCard); + } + } + } + // since this is LiveData<Void> + return null; + }); } + @AnyThread public WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) { WrappedLiveData<Label> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -961,7 +1240,8 @@ public class SyncManager { return liveData; } - public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, Label label, long localCardId) { + @AnyThread + public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId) { MutableLiveData<Label> liveData = new MutableLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(accountId); @@ -986,7 +1266,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<Void> deleteLabel(Label label) { + @AnyThread + public WrappedLiveData<Void> deleteLabel(@NonNull Label label) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(label.getAccountId()); @@ -997,7 +1278,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<Label> updateLabel(Label label) { + @AnyThread + public WrappedLiveData<Label> updateLabel(@NonNull Label label) { WrappedLiveData<Label> liveData = new WrappedLiveData<>(); doAsync(() -> { Account account = dataBaseAdapter.getAccountByIdDirectly(label.getAccountId()); @@ -1008,7 +1290,8 @@ public class SyncManager { return liveData; } - public void assignUserToCard(User user, Card card) { + @AnyThread + public void assignUserToCard(@NonNull User user, @NonNull Card card) { doAsync(() -> { final long localUserId = user.getLocalId(); final long localCardId = card.getLocalId(); @@ -1032,7 +1315,8 @@ public class SyncManager { }); } - public void assignLabelToCard(Label label, Card card) { + @AnyThread + public void assignLabelToCard(@NonNull Label label, @NonNull Card card) { doAsync(() -> { final long localLabelId = label.getLocalId(); final long localCardId = card.getLocalId(); @@ -1055,7 +1339,8 @@ public class SyncManager { }); } - public void unassignLabelFromCard(Label label, Card card) { + @AnyThread + public void unassignLabelFromCard(@NonNull Label label, @NonNull Card card) { doAsync(() -> { dataBaseAdapter.deleteJoinedLabelForCard(card.getLocalId(), label.getLocalId()); Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getStackId()); @@ -1072,7 +1357,8 @@ public class SyncManager { }); } - public void unassignUserFromCard(User user, Card card) { + @AnyThread + public void unassignUserFromCard(@NonNull User user, @NonNull Card card) { doAsync(() -> { dataBaseAdapter.deleteJoinedUserForCard(card.getLocalId(), user.getLocalId()); if (serverAdapter.hasInternetConnection()) { @@ -1122,12 +1408,13 @@ public class SyncManager { return dataBaseAdapter.getUserByUid(accountId, uid); } + @WorkerThread public User getUserByUidDirectly(long accountId, String uid) { return dataBaseAdapter.getUserByUidDirectly(accountId, uid); } - public LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long notYetAssignedToLocalCardId, final String searchTerm) { - return dataBaseAdapter.searchUserByUidOrDisplayName(accountId, notYetAssignedToLocalCardId, searchTerm); + public LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, final String searchTerm) { + return dataBaseAdapter.searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, searchTerm); } public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedInACL, final String searchTerm) { @@ -1150,11 +1437,11 @@ public class SyncManager { return dataBaseAdapter.createUser(accountId, user); } - public void updateUser(long accountId, User user) { + public void updateUser(long accountId, @NonNull User user) { dataBaseAdapter.updateUser(accountId, user, true); } - public LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, String searchTerm) { + public LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, @NonNull String searchTerm) { return dataBaseAdapter.searchNotYetAssignedLabelsByTitle(accountId, boardId, notYetAssignedToLocalCardId, searchTerm); } @@ -1165,7 +1452,8 @@ public class SyncManager { /** * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/360">reenable reorder</a> */ - public void reorder(long accountId, FullCard movedCard, long newStackId, int newIndex) { + @AnyThread + public void reorder(long accountId, @NonNull FullCard movedCard, long newStackId, int newIndex) { doAsync(() -> { // read cards of new stack List<FullCard> cardsOfNewStack = dataBaseAdapter.getFullCardsForStackDirectly(accountId, newStackId); @@ -1250,7 +1538,7 @@ public class SyncManager { } - private void reorderLocally(List<FullCard> cardsOfNewStack, FullCard movedCard, long newStackId, int newOrder) { + private void reorderLocally(List<FullCard> cardsOfNewStack, @NonNull FullCard movedCard, long newStackId, int newOrder) { // set new stack and order Card movedInnerCard = movedCard.getCard(); int oldOrder = movedInnerCard.getOrder(); @@ -1308,7 +1596,7 @@ public class SyncManager { reorderAscending(movedInnerCard, changedCards, startingAtOrder); } - private void reorderAscending(Card movedCard, List<Card> cardsToReorganize, int startingAtOrder) { + private void reorderAscending(@NonNull Card movedCard, @NonNull List<Card> cardsToReorganize, int startingAtOrder) { Date now = new Date(); for (Card card : cardsToReorganize) { card.setOrder(startingAtOrder); @@ -1332,6 +1620,7 @@ public class SyncManager { * The problem is, that the attachment is still in our local database and everytime one tries to sync, the log is spammed with 500 errors * Also this leads to the attachment being present in the card forever with a DBStatus.LOCAL_EDITED */ + @AnyThread public WrappedLiveData<Attachment> addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file) { WrappedLiveData<Attachment> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -1351,7 +1640,8 @@ public class SyncManager { return liveData; } - public WrappedLiveData<Attachment> updateAttachmentForCard(long accountId, Attachment existing, @NonNull String mimeType, @NonNull File file) { + @AnyThread + public WrappedLiveData<Attachment> updateAttachmentForCard(long accountId, @NonNull Attachment existing, @NonNull String mimeType, @NonNull File file) { WrappedLiveData<Attachment> liveData = new WrappedLiveData<>(); doAsync(() -> { Attachment attachment = populateAttachmentEntityForFile(existing, existing.getCardId(), mimeType, file); @@ -1378,7 +1668,8 @@ public class SyncManager { return liveData; } - private Attachment populateAttachmentEntityForFile(Attachment target, long localCardId, @NonNull String mimeType, @NonNull File file) { + @AnyThread + private static Attachment populateAttachmentEntityForFile(@NonNull Attachment target, long localCardId, @NonNull String mimeType, @NonNull File file) { Attachment attachment = target; attachment.setCardId(localCardId); attachment.setMimetype(mimeType); @@ -1390,6 +1681,7 @@ public class SyncManager { return attachment; } + @AnyThread public WrappedLiveData<Void> deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId) { WrappedLiveData<Void> liveData = new WrappedLiveData<>(); doAsync(() -> { @@ -1415,6 +1707,7 @@ public class SyncManager { * Can be called from a configuration screen or a picker. * Creates a new entry in the database, if row with given widgetId does not yet exist. */ + @AnyThread public void addOrUpdateSingleCardWidget(int widgetId, long accountId, long boardId, long localCardId) { doAsync(() -> dataBaseAdapter.createSingleCardWidget(widgetId, accountId, boardId, localCardId)); } @@ -1428,6 +1721,7 @@ public class SyncManager { return model; } + @AnyThread public void deleteSingleCardWidgetModel(int widgetId) { doAsync(() -> dataBaseAdapter.deleteSingleCardWidget(widgetId)); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java index 25c8b726b..f43cbd34f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java @@ -36,11 +36,11 @@ public class SyncWorker extends Worker { SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); SyncManager syncManager = new SyncManager(getApplicationContext(), null); if (syncManager.hasInternetConnection()) { - DeckLog.log("Starting background synchronization"); + DeckLog.info("Starting background synchronization"); sharedPreferencesEditor.putLong(getApplicationContext().getString(R.string.shared_preference_last_background_sync), System.currentTimeMillis()); sharedPreferencesEditor.apply(); boolean success = syncManager.synchronizeEverything(); - DeckLog.log("Finishing background synchronization with result " + success); + DeckLog.info("Finishing background synchronization with result " + success); return success ? Result.failure() : Result.success(); } return Result.success(); @@ -53,25 +53,30 @@ public class SyncWorker extends Worker { public static void update(@NonNull Context context, String preferenceValue) { deregister(context); - if (!context.getString(R.string.pref_value_background_sync_off).equals(preferenceValue)) { - int repeatInterval = 15; - TimeUnit unit = TimeUnit.MINUTES; - if (context.getString(R.string.pref_value_background_1_hour).equals(preferenceValue)) { - repeatInterval = 1; - unit = TimeUnit.HOURS; - } else if (context.getString(R.string.pref_value_background_6_hours).equals(preferenceValue)) { - repeatInterval = 6; - unit = TimeUnit.HOURS; - } + int repeatInterval = -1; + TimeUnit unit = null; + if (context.getString(R.string.pref_value_background_15_minutes).equals(preferenceValue)) { + repeatInterval = 15; + unit = TimeUnit.MINUTES; + } else if (context.getString(R.string.pref_value_background_1_hour).equals(preferenceValue)) { + repeatInterval = 1; + unit = TimeUnit.HOURS; + } else if (context.getString(R.string.pref_value_background_6_hours).equals(preferenceValue)) { + repeatInterval = 6; + unit = TimeUnit.HOURS; + } + if (unit == null) { + DeckLog.info("Do not register a new " + SyncWorker.class.getSimpleName() + " because setting " + preferenceValue + " is not a valid time frame"); + } else { final PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, repeatInterval, unit) .setConstraints(constraints).build(); - DeckLog.log("Registering worker running each " + repeatInterval + " " + unit); + DeckLog.info("Registering " + SyncWorker.class.getSimpleName() + " running each " + repeatInterval + " " + unit); WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(SyncWorker.WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, work); } } private static void deregister(@NonNull Context context) { - DeckLog.log("Deregistering all workers with tag \"" + WORKER_TAG + "\""); + DeckLog.info("Deregistering all " + SyncWorker.class.getSimpleName() + " with tag \"" + WORKER_TAG + "\""); WorkManager.getInstance(context.getApplicationContext()).cancelAllWorkByTag(WORKER_TAG); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java index 1e2465eb4..4f41173e9 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java @@ -39,6 +39,8 @@ import it.niedermann.nextcloud.deck.model.full.FullStack; import it.niedermann.nextcloud.deck.model.ocs.Capabilities; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser; +import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList; import it.niedermann.nextcloud.deck.model.propagation.CardUpdate; import it.niedermann.nextcloud.deck.model.propagation.Reorder; import it.niedermann.nextcloud.deck.util.DateUtil; @@ -151,6 +153,15 @@ public class ServerAdapter { ensureInternetConnection(); RequestHelper.request(provider, () -> provider.getNextcloudAPI().getCapabilities(), responseCallback); } + public void getAllOcsUsers(IResponseCallback<OcsUserList> responseCallback) { + ensureInternetConnection(); + RequestHelper.request(provider, () -> provider.getNextcloudAPI().getAllUsers(), responseCallback); + } + + public void getOcsUserDetails(String ocsUserName, IResponseCallback<OcsUser> responseCallback) { + ensureInternetConnection(); + RequestHelper.request(provider, () -> provider.getNextcloudAPI().getUserDetails(ocsUserName), responseCallback); + } public void getActivitiesForCard(long cardId, IResponseCallback<List<it.niedermann.nextcloud.deck.model.ocs.Activity>> responseCallback) { ensureInternetConnection(); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java index 8ef7e876d..c643289bd 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java @@ -3,6 +3,8 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import androidx.lifecycle.LiveData; import androidx.sqlite.db.SimpleSQLiteQuery; @@ -36,8 +38,8 @@ import it.niedermann.nextcloud.deck.model.internal.FilterInformation; import it.niedermann.nextcloud.deck.model.ocs.Activity; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.Mention; -import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel; import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment; +import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData; import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget; @@ -123,7 +125,7 @@ public class DataBaseAdapter { return db.getCardDao().getFullCardByLocalIdDirectly(accountId, localId); } - public void filterRelationsForCard(FullCard card) { + public void filterRelationsForCard(@Nullable FullCard card) { if (card != null) { if (card.getLabels() != null && !card.getLabels().isEmpty()) { List<Long> filteredIDs = db.getJoinCardWithLabelDao().filterDeleted(card.getLocalId(), getLocalIDs(card.getLabels())); @@ -136,7 +138,7 @@ public class DataBaseAdapter { } } - private <T> List<Long> getLocalIDs(List<? extends AbstractRemoteEntity> remoteEntityList) { + private <T> List<Long> getLocalIDs(@NonNull List<? extends AbstractRemoteEntity> remoteEntityList) { ArrayList<Long> ids = new ArrayList<>(remoteEntityList.size()); for (AbstractRemoteEntity entity : remoteEntityList) { ids.add(entity.getLocalId()); @@ -144,7 +146,7 @@ public class DataBaseAdapter { return ids; } - public void readRelationsForACL(List<AccessControl> acl) { + public void readRelationsForACL(@Nullable List<AccessControl> acl) { if (acl != null) { for (AccessControl accessControl : acl) { readRelationsForACL(accessControl); @@ -152,7 +154,7 @@ public class DataBaseAdapter { } } - public void readRelationsForACL(AccessControl acl) { + public void readRelationsForACL(@Nullable AccessControl acl) { if (acl != null) { if (acl.getUserId() != null) { acl.setUser(db.getUserDao().getUserByLocalIdDirectly(acl.getUserId())); @@ -160,7 +162,7 @@ public class DataBaseAdapter { } } - private void filterRelationsForCard(List<FullCard> card) { + private void filterRelationsForCard(@Nullable List<FullCard> card) { if (card == null) { return; } @@ -169,6 +171,7 @@ public class DataBaseAdapter { } } + @WorkerThread public Card getCardByRemoteIdDirectly(long accountId, long remoteId) { return db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteId); } @@ -221,7 +224,7 @@ public class DataBaseAdapter { } - private void fillSqlWithListValues(StringBuilder query, List<Object> args, List<? extends IRemoteEntity> entities) { + private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) { for (int i = 0; i < entities.size(); i++) { if (i > 0) { query.append(", "); @@ -231,10 +234,12 @@ public class DataBaseAdapter { } } + @WorkerThread public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId) { return db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId); } + @WorkerThread public User getUserByUidDirectly(long accountId, String uid) { return db.getUserDao().getUserByUidDirectly(accountId, uid); } @@ -254,11 +259,12 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getLabelDao().getLabelByRemoteId(accountId, remoteId)); } + @WorkerThread public Label getLabelByRemoteIdDirectly(long accountId, long remoteId) { return db.getLabelDao().getLabelByRemoteIdDirectly(accountId, remoteId); } - public long createLabel(long accountId, Label label) { + public long createLabel(long accountId, @NonNull Label label) { label.setAccountId(accountId); return db.getLabelDao().insert(label); } @@ -356,7 +362,6 @@ public class DataBaseAdapter { return LiveDataHelper.wrapInLiveData(() -> { long id = db.getAccountDao().insert(account); return readAccountDirectly(id); - }); } @@ -377,6 +382,7 @@ public class DataBaseAdapter { // return distinctUntilChanged(db.getAccountDao().getAccountByName(name)); } + @WorkerThread public Account readAccountDirectly(long id) { return db.getAccountDao().getAccountByIdDirectly(id); } @@ -396,7 +402,7 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getBoardDao().getBoardsWithEditPermissionsForAccount(accountId)); } - public WrappedLiveData<Board> createBoard(long accountId, Board board) { + public WrappedLiveData<Board> createBoard(long accountId, @NonNull Board board) { return LiveDataHelper.wrapInLiveData(() -> { board.setAccountId(accountId); long id = db.getBoardDao().insert(board); @@ -405,7 +411,8 @@ public class DataBaseAdapter { }); } - public long createBoardDirectly(long accountId, Board board) { + @WorkerThread + public long createBoardDirectly(long accountId, @NonNull Board board) { board.setAccountId(accountId); return db.getBoardDao().insert(board); } @@ -428,6 +435,7 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getStackDao().getFullStacksForBoard(accountId, localBoardId)); } + @WorkerThread public List<FullStack> getFullStacksForBoardDirectly(long accountId, long localBoardId) { return db.getStackDao().getFullStacksForBoardDirectly(accountId, localBoardId); } @@ -455,6 +463,7 @@ public class DataBaseAdapter { db.getStackDao().update(stack); } + @WorkerThread public Card getCardByLocalIdDirectly(long accountId, long localCardId) { return db.getCardDao().getCardByLocalIdDirectly(accountId, localCardId); } @@ -463,10 +472,12 @@ public class DataBaseAdapter { return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardByLocalId(accountId, localCardId), this::filterRelationsForCard); } + @WorkerThread public List<FullCard> getLocallyChangedCardsDirectly(long accountId) { return db.getCardDao().getLocallyChangedCardsDirectly(accountId); } + @WorkerThread public List<FullCard> getLocallyChangedCardsByLocalStackIdDirectly(long accountId, long localStackId) { return db.getCardDao().getLocallyChangedCardsByLocalStackIdDirectly(accountId, localStackId); } @@ -493,7 +504,7 @@ public class DataBaseAdapter { db.getCardDao().delete(card); } - public void updateCard(Card card, boolean setStatus) { + public void updateCard(@NonNull Card card, boolean setStatus) { markAsEditedIfNeeded(card, setStatus); db.getCardDao().update(card); if (db.getSingleCardWidgetModelDao().containsCardLocalId(card.getLocalId())) { @@ -502,19 +513,22 @@ public class DataBaseAdapter { } } - public long createAccessControl(long accountId, AccessControl entity) { + public long createAccessControl(long accountId, @NonNull AccessControl entity) { entity.setAccountId(accountId); return db.getAccessControlDao().insert(entity); } + @WorkerThread public AccessControl getAccessControlByRemoteIdDirectly(long accountId, Long id) { return db.getAccessControlDao().getAccessControlByRemoteIdDirectly(accountId, id); } - public LiveData<List<AccessControl>> getAccessControlByLocalBoardId(long accountId, Long id) { - return LiveDataHelper.interceptLiveData(db.getAccessControlDao().getAccessControlByLocalBoardId(accountId, id), (acl) -> { - readRelationsForACL(acl); - }); + public LiveData<List<AccessControl>> getAccessControlByLocalBoardId(long accountId, Long localBoardId) { + return LiveDataHelper.interceptLiveData(db.getAccessControlDao().getAccessControlByLocalBoardId(accountId, localBoardId), this::readRelationsForACL); + } + + public List<AccessControl> getAccessControlByLocalBoardIdDirectly(long accountId, Long localBoardId) { + return db.getAccessControlDao().getAccessControlByLocalBoardIdDirectly(accountId, localBoardId); } public void updateAccessControl(AccessControl entity, boolean setStatus) { @@ -535,6 +549,7 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getBoardDao().getFullBoardById(accountId, localId)); } + @WorkerThread public Board getBoardByLocalIdDirectly(long localId) { return db.getBoardDao().getBoardByIdDirectly(localId); } @@ -551,9 +566,9 @@ public class DataBaseAdapter { return db.getUserDao().getUsersForAccount(accountId); } - public LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long notYetAssignedToLocalCardId, final String searchTerm) { + public LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, final String searchTerm) { validateSearchTerm(searchTerm); - return db.getUserDao().searchUserByUidOrDisplayName(accountId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%"); + return db.getUserDao().searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%"); } public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedToACL, final String searchTerm) { @@ -578,34 +593,38 @@ public class DataBaseAdapter { return db.getLabelDao().findProposalsForLabelsToAssign(accountId, boardId, notAssignedToLocalCardId); } - + @WorkerThread public Attachment getAttachmentByRemoteIdDirectly(long accountId, Long id) { return db.getAttachmentDao().getAttachmentByRemoteIdDirectly(accountId, id); } + @WorkerThread public Attachment getAttachmentByLocalIdDirectly(long accountId, Long id) { return db.getAttachmentDao().getAttachmentByLocalIdDirectly(accountId, id); } + @WorkerThread public List<Attachment> getAttachmentsForLocalCardIdDirectly(long accountId, Long localCardId) { return db.getAttachmentDao().getAttachmentsForLocalCardIdDirectly(accountId, localCardId); } + @WorkerThread public List<Attachment> getLocallyChangedAttachmentsByLocalCardIdDirectly(long accountId, Long localCardId) { return db.getAttachmentDao().getLocallyChangedAttachmentsByLocalCardIdDirectly(accountId, localCardId); } + @WorkerThread public List<Attachment> getLocallyChangedAttachmentsDirectly(long accountId) { return db.getAttachmentDao().getLocallyChangedAttachmentsDirectly(accountId); } - public long createAttachment(long accountId, Attachment attachment) { + public long createAttachment(long accountId, @NonNull Attachment attachment) { attachment.setAccountId(accountId); attachment.setCreatedAt(new Date()); return db.getAttachmentDao().insert(attachment); } - public void updateAttachment(long accountId, Attachment attachment, boolean setStatus) { + public void updateAttachment(long accountId, @NonNull Attachment attachment, boolean setStatus) { markAsEditedIfNeeded(attachment, setStatus); attachment.setAccountId(accountId); db.getAttachmentDao().update(attachment); @@ -621,20 +640,23 @@ public class DataBaseAdapter { } } - private void validateSearchTerm(String searchTerm) { + private void validateSearchTerm(@Nullable String searchTerm) { if (searchTerm == null || searchTerm.trim().length() < 1) { throw new IllegalArgumentException("please provide a proper search term! \"" + searchTerm + "\" doesn't seem right..."); } } + @WorkerThread public Account getAccountByIdDirectly(long accountId) { return db.getAccountDao().getAccountByIdDirectly(accountId); } + @WorkerThread public List<Account> getAllAccountsDirectly() { return db.getAccountDao().getAllAccountsDirectly(); } + @WorkerThread public User getUserByLocalIdDirectly(long localUserId) { return db.getUserDao().getUserByLocalIdDirectly(localUserId); } @@ -647,6 +669,7 @@ public class DataBaseAdapter { db.getJoinCardWithLabelDao().setDbStatus(localCardId, localLabelId, status); } + @WorkerThread public Label getLabelByLocalIdDirectly(long localLabelId) { return db.getLabelDao().getLabelsByIdDirectly(localLabelId); } @@ -675,10 +698,16 @@ public class DataBaseAdapter { return db.getLabelDao().getLocallyChangedLabelsDirectly(accountId); } + @WorkerThread public Board getBoardByLocalCardIdDirectly(long localCardId) { return db.getBoardDao().getBoardByLocalCardIdDirectly(localCardId); } + @WorkerThread + public FullBoard getFullBoardByLocalCardIdDirectly(long localCardId) { + return db.getBoardDao().getFullBoardByLocalCardIdDirectly(localCardId); + } + public JoinCardWithLabel getJoinCardWithLabel(Long localLabelId, Long localCardId) { return db.getJoinCardWithLabelDao().getJoin(localLabelId, localCardId); } @@ -720,6 +749,7 @@ public class DataBaseAdapter { return db.getActivityDao().insert(activity); } + @WorkerThread public Activity getActivityByRemoteIdDirectly(long accountId, long remoteActivityId) { return db.getActivityDao().getActivityByRemoteIdDirectly(accountId, remoteActivityId); } @@ -760,10 +790,12 @@ public class DataBaseAdapter { }); } + @WorkerThread public DeckComment getCommentByRemoteIdDirectly(long accountId, Long remoteCommentId) { return db.getCommentDao().getCommentByRemoteIdDirectly(accountId, remoteCommentId); } + @WorkerThread public DeckComment getCommentByLocalIdDirectly(long accountId, Long localCommentId) { return db.getCommentDao().getCommentByLocalIdDirectly(accountId, localCommentId); } @@ -787,6 +819,7 @@ public class DataBaseAdapter { } } + @WorkerThread public List<DeckComment> getLocallyChangedCommentsByLocalCardIdDirectly(long accountId, long localCardId) { return db.getCommentDao().getLocallyChangedCommentsByLocalCardIdDirectly(accountId, localCardId); } @@ -799,6 +832,7 @@ public class DataBaseAdapter { return db.getMentionDao().insert(mention); } + @WorkerThread public List<DeckComment> getCommentByLocalCardIdDirectly(Long localCardId) { return db.getCommentDao().getCommentByLocalCardIdDirectly(localCardId); } @@ -807,6 +841,7 @@ public class DataBaseAdapter { return db.getCardDao().getCardsWithLocallyChangedCommentsDirectly(accountId); } + @WorkerThread public Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId) { return db.getStackDao().getLocalStackIdByRemoteStackIdDirectly(accountId, stackId); } @@ -823,6 +858,7 @@ public class DataBaseAdapter { return db.getJoinCardWithLabelDao().countCardsWithLabel(localLabelId); } + @WorkerThread public Label getLabelByBoardIdAndTitleDirectly(long boardId, String title) { return db.getLabelDao().getLabelByBoardIdAndTitleDirectly(boardId, title); } @@ -835,9 +871,12 @@ public class DataBaseAdapter { return LiveDataHelper.postCustomValue(distinctUntilChanged(db.getBoardDao().countArchivedBoards(accountId)), data -> data != null && data > 0); } + @WorkerThread public Long getRemoteCommentIdForLocalIdDirectly(Long localCommentId) { return db.getCommentDao().getRemoteCommentIdForLocalIdDirectly(localCommentId); } + + @WorkerThread public Long getLocalCommentIdForRemoteIdDirectly(long accountId, Long remoteCommentId) { return db.getCommentDao().getLocalCommentIdForRemoteIdDirectly(accountId, remoteCommentId); } @@ -847,6 +886,7 @@ public class DataBaseAdapter { // Widgets // ------------------- + @WorkerThread public long createSingleCardWidget(int widgetId, long accountId, long boardLocalId, long cardLocalId) { SingleCardWidgetModel model = new SingleCardWidgetModel(); model.setWidgetId(widgetId); @@ -869,4 +909,12 @@ public class DataBaseAdapter { model.setWidgetId(widgetId); db.getSingleCardWidgetModelDao().delete(model); } + + public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) { + return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%"+host+"%", boardRemoteId); + } + + public List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String host, long boardRemoteId) { + return db.getAccountDao().readAccountsForHostWithReadAccessToBoardDirectly("%"+host+"%", boardRemoteId); + } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java index 82fb86c6e..0e86557fa 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java @@ -4,6 +4,7 @@ import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; @@ -32,6 +33,7 @@ import it.niedermann.nextcloud.deck.model.ocs.Activity; import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.Mention; import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel; +import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccessControlDao; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccountDao; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.ActivityDao; @@ -73,7 +75,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao; SingleCardWidgetModel.class, }, exportSchema = false, - version = 14 + version = 15 ) @TypeConverters({DateTypeConverter.class}) public abstract class DeckDatabase extends RoomDatabase { @@ -201,6 +203,19 @@ public abstract class DeckDatabase extends RoomDatabase { .addMigrations(MIGRATION_11_12) .addMigrations(MIGRATION_12_13) .addMigrations(MIGRATION_13_14) + .addMigrations(new Migration(14, 15) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + // https://github.com/stefan-niedermann/nextcloud-deck/issues/570 + SyncWorker.update(context); + // https://github.com/stefan-niedermann/nextcloud-deck/issues/525 + PreferenceManager + .getDefaultSharedPreferences(context) + .edit() + .remove("it.niedermann.nextcloud.deck.theme_text") + .apply(); + } + }) .fallbackToDestructiveMigration() .addCallback(ON_CREATE_CALLBACK) .build(); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccessControlDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccessControlDao.java index 9dc9b7b3d..2ed1e7d59 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccessControlDao.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccessControlDao.java @@ -17,6 +17,9 @@ public interface AccessControlDao extends GenericDao<AccessControl> { @Query("SELECT * FROM AccessControl WHERE accountId = :accountId and boardId = :localBoardId and status <> 3") LiveData<List<AccessControl>> getAccessControlByLocalBoardId(final long accountId, final long localBoardId); + @Query("SELECT * FROM AccessControl WHERE accountId = :accountId and boardId = :localBoardId and status <> 3") + List<AccessControl> getAccessControlByLocalBoardIdDirectly(final long accountId, final long localBoardId); + @Query("SELECT * FROM AccessControl WHERE accountId = :accountId and id = :remoteId") AccessControl getAccessControlByRemoteIdDirectly(final long accountId, final long remoteId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java index 515ecc0a4..6ebe4a8f3 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java @@ -36,4 +36,10 @@ public interface AccountDao extends GenericDao<Account> { @Query("SELECT * from account") List<Account> getAllAccountsDirectly(); + + @Query("SELECT * from account a where a.url like :hostLike and exists (select 1 from board b where b.id = :boardRemoteId and a.id = b.accountId)") + LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String hostLike, long boardRemoteId); + + @Query("SELECT * from account a where a.url like :hostLike and exists (select 1 from board b where b.id = :boardRemoteId and a.id = b.accountId)") + List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String hostLike, long boardRemoteId); }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java index 0f4117481..bd189e846 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java @@ -52,6 +52,9 @@ public interface BoardDao extends GenericDao<Board> { @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId") Board getBoardByLocalCardIdDirectly(long localCardId); + @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId") + FullBoard getFullBoardByLocalCardIdDirectly(long localCardId); + @Transaction @Query("SELECT * FROM board WHERE accountId = :accountId") List<FullBoard> getAllFullBoards(long accountId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java index e9b9ee65b..6f712d07f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java @@ -26,8 +26,19 @@ public interface UserDao extends GenericDao<User> { " where ju.userId = u.localId" + " and ju.cardId = :notYetAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED " )" + + " AND" + + " (" + + " EXISTS (" + + " select 1 from accesscontrol" + + " where userId = u.localId and boardId = :boardId" + + " )" + + " OR" + + " EXISTS (" + + " select 1 from board where localId = :boardId AND ownerId = u.localId" + + " )" + + ")" + "and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm )") - LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long notYetAssignedToLocalCardId, final String searchTerm); + LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, final String searchTerm); @Query("SELECT u.* FROM user u WHERE accountId = :accountId " + " AND NOT EXISTS (" + diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java index 650c903b3..10a18bb09 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java @@ -85,7 +85,13 @@ public class LiveDataHelper { public static <T> WrappedLiveData<T> wrapInLiveData(final LiveDataWrapper<T> liveDataWrapper) { final WrappedLiveData<T> liveData = new WrappedLiveData<>(); - doAsync(() -> liveDataWrapper.postResult(liveData)); + doAsync(() -> { + try { + liveDataWrapper.postResult(liveData); + } catch (Throwable t) { + liveData.postError(t); + } + }); return liveData; } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java new file mode 100644 index 000000000..326d257ab --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java @@ -0,0 +1,32 @@ +package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial; + +import java.util.List; + +import it.niedermann.nextcloud.deck.api.IResponseCallback; +import it.niedermann.nextcloud.deck.model.AccessControl; +import it.niedermann.nextcloud.deck.model.full.FullBoard; +import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter; +import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter; +import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper; +import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AccessControlDataProvider; +import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.BoardDataProvider; + +public class BoardWitAclDownSyncDataProvider extends BoardDataProvider { + + @Override + public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) { + + List<AccessControl> acl = entityFromServer.getParticipants(); + if (acl != null && !acl.isEmpty()){ + for (AccessControl ac : acl){ + ac.setBoardId(existingEntity.getLocalId()); + } + syncHelper.doSyncFor(new AccessControlDataProvider(this, existingEntity, acl)); + } + } + + @Override + public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) { + // do nothing! + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java index ae7ab3683..854b99a6a 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java @@ -4,13 +4,17 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteConstraintException; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; import androidx.preference.PreferenceManager; import com.nextcloud.android.sso.AccountImporter; @@ -81,6 +85,12 @@ public class ImportAccountActivity extends AppCompatActivity { AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); } }); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + Drawable wrapDrawable = DrawableCompat.wrap(binding.progressCircular.getIndeterminateDrawable()); + DrawableCompat.setTint(wrapDrawable, ContextCompat.getColor(this, R.color.defaultBrand)); + binding.progressCircular.setIndeterminateDrawable(DrawableCompat.unwrap(wrapDrawable)); + } } @Override diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java index c29994e64..b5713909b 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteConstraintException; import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; @@ -26,6 +25,7 @@ import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.util.Pair; import androidx.core.view.GravityCompat; @@ -53,7 +53,6 @@ import java.util.Objects; import it.niedermann.android.crosstabdnd.CrossTabDragAndDrop; import it.niedermann.android.tablayouthelper.TabLayoutHelper; import it.niedermann.android.tablayouthelper.TabTitleGenerator; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.api.IResponseCallback; @@ -97,12 +96,24 @@ import it.niedermann.nextcloud.deck.ui.stack.StackAdapter; import it.niedermann.nextcloud.deck.ui.stack.StackFragment; import it.niedermann.nextcloud.deck.util.DrawerMenuUtil; -import static android.graphics.Color.parseColor; import static androidx.lifecycle.Transformations.switchMap; -import static it.niedermann.nextcloud.deck.Application.NO_ACCOUNT_ID; -import static it.niedermann.nextcloud.deck.Application.NO_BOARD_ID; -import static it.niedermann.nextcloud.deck.Application.NO_STACK_ID; +import static it.niedermann.nextcloud.deck.DeckApplication.NO_ACCOUNT_ID; +import static it.niedermann.nextcloud.deck.DeckApplication.NO_BOARD_ID; +import static it.niedermann.nextcloud.deck.DeckApplication.NO_STACK_ID; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentBoardId; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentStackId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentBoardId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentStackId; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToPrimaryTabLayout; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.clearBrandColors; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.saveBrandColors; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficient; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas; import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ABOUT; import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ADD_BOARD; import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ARCHIVED_BOARDS; @@ -159,6 +170,8 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); + setTheme(R.style.AppTheme); + binding = ActivityMainBinding.inflate(getLayoutInflater()); headerBinding = NavHeaderMainBinding.bind(binding.navigationView.getHeaderView(0)); setContentView(binding.getRoot()); @@ -190,18 +203,18 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener } }).observe(this, (List<Account> accounts) -> { if (accounts == null || accounts.size() == 0) { - // Last account has been deleted. hasAccounts LiveData will handle this, but we make sure, that branding is reset. - Application.saveBrandColors(this, getResources().getColor(R.color.primary), Color.WHITE); + // Last account has been deleted. hasAccounts LiveData will handle this, but we make sure, that branding is reset. + saveBrandColors(this, ContextCompat.getColor(this, R.color.defaultBrand)); return; } accountsList = accounts; - long lastAccountId = Application.readCurrentAccountId(this); + long lastAccountId = readCurrentAccountId(this); for (Account account : accountsList) { if (lastAccountId == account.getId() || lastAccountId == NO_ACCOUNT_ID) { - mainViewModel.setCurrentAccount(account, account.getServerDeckVersionAsObject().isSupported(this)); + mainViewModel.setCurrentAccount(account); if (!firstAccountAdded) { DeckLog.info("Syncing the current account on app start"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -229,13 +242,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener SingleAccountHelper.setCurrentAccount(getApplicationContext(), mainViewModel.getCurrentAccount().getName()); syncManager = new SyncManager(this); - Application.saveBrandColors(this, parseColor(mainViewModel.getCurrentAccount().getColor()), parseColor(mainViewModel.getCurrentAccount().getTextColor())); - Application.saveCurrentAccountId(this, mainViewModel.getCurrentAccount().getId()); + saveCurrentAccountId(this, mainViewModel.getCurrentAccount().getId()); if (mainViewModel.getCurrentAccount().isMaintenanceEnabled()) { refreshCapabilities(mainViewModel.getCurrentAccount()); } - lastBoardId = Application.readCurrentBoardId(this, mainViewModel.getCurrentAccount().getId()); + lastBoardId = readCurrentBoardId(this, mainViewModel.getCurrentAccount().getId()); if (boardsLiveData != null && boardsLiveDataObserver != null) { boardsLiveData.removeObserver(boardsLiveDataObserver); @@ -262,6 +274,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener setCurrentBoard(boardsList.get(0)); } } else { + clearBrandColors(this); clearCurrentBoard(); } @@ -277,13 +290,11 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener }; boardsLiveData.observe(this, boardsLiveDataObserver); - final Drawable placeholderAvatar = getResources().getDrawable(R.drawable.ic_baseline_account_circle_24); - DrawableCompat.setTint(placeholderAvatar, parseColor(currentAccount.getTextColor())); Glide .with(binding.accountSwitcher.getContext()) .load(currentAccount.getAvatarUrl(64)) - .placeholder(placeholderAvatar) - .error(placeholderAvatar) + .placeholder(R.drawable.ic_baseline_account_circle_24) + .error(R.drawable.ic_baseline_account_circle_24) .apply(RequestOptions.circleCropTransform()) .into(binding.accountSwitcher); @@ -345,7 +356,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener binding.viewPager.post(() -> { // stackAdapter size might differ from position when an account has been deleted if (stackAdapter.getItemCount() > position) { - Application.saveCurrentStackId(getApplicationContext(), mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), stackAdapter.getItem(position).getLocalId()); + saveCurrentStackId(getApplicationContext(), mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), stackAdapter.getItem(position).getLocalId()); } else { DeckLog.logError(new IllegalStateException("Tried to save current Stack which cannot be available (stackAdapter doesn't have this position)")); } @@ -362,11 +373,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener } }); filterViewModel.getFilterInformation().observe(this, (info) -> - binding.filter.setImageDrawable(getResources().getDrawable( - filterViewModel.getFilterInformation().getValue() == null - ? R.drawable.ic_filter_list_white_24dp - : R.drawable.ic_filter_list_active_white_24dp) - )); + binding.filterIndicator.setVisibility(filterViewModel.getFilterInformation().getValue() == null ? View.GONE : View.VISIBLE)); binding.filter.setOnClickListener((v) -> FilterDialogFragment.newInstance().show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName())); binding.archivedCards.setOnClickListener((v) -> startActivity(ArchivedCardsActvitiy.createIntent(this, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), mainViewModel.currentBoardHasEditPermission()))); @@ -411,23 +418,16 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener } @Override - public void applyBrand(@ColorInt int mainColor, @ColorInt int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - applyBrandToPrimaryTabLayout(mainColor, textColor, binding.stackTitles); - applyBrandToFAB(mainColor, textColor, binding.fab); - - // Is null as soon as the avatar has been set -// @Nullable -// Drawable accountSwitcherDrawable = binding.accountSwitcher.getDrawable(); -// if (accountSwitcherDrawable != null) { -// DrawableCompat.setTint(accountSwitcherDrawable, textColor); -// } - - binding.listMenuButton.setBackgroundColor(mainColor); - binding.listMenuButton.setColorFilter(textColor); - + public void applyBrand(@ColorInt int mainColor) { + applyBrandToPrimaryTabLayout(mainColor, binding.stackTitles); + applyBrandToFAB(mainColor, binding.fab); + // TODO We assume, that the background of the spinner is always white + binding.swipeRefreshLayout.setColorSchemeColors(contrastRatioIsSufficient(Color.WHITE, mainColor) ? mainColor : colorAccent); headerBinding.headerView.setBackgroundColor(mainColor); - headerBinding.appName.setTextColor(textColor); + @ColorInt final int headerTextColor = contrastRatioIsSufficientBigAreas(mainColor, Color.WHITE) ? Color.WHITE : Color.BLACK; + DrawableCompat.setTint(headerBinding.logo.getDrawable(), headerTextColor); + headerBinding.appName.setTextColor(headerTextColor); + DrawableCompat.setTint(binding.filterIndicator.getDrawable(), getSecondaryForegroundColorDependingOnTheme(this, mainColor)); } @Override @@ -522,9 +522,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener if (mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().isSupported(MainActivity.this) && !response.getDeckVersion().isSupported(MainActivity.this)) { recreate(); } - @ColorInt final int mainColor = parseColor(response.getColor()); - @ColorInt final int textColor = parseColor(response.getTextColor()); - runOnUiThread(() -> Application.saveBrandColors(MainActivity.this, mainColor, textColor)); } } @@ -551,11 +548,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener if (stacksLiveData != null) { stacksLiveData.removeObservers(this); } + saveBrandColors(this, Color.parseColor('#' + board.getColor())); mainViewModel.setCurrentBoard(board); filterViewModel.clearFilterInformation(); lastBoardId = board.getLocalId(); - Application.saveCurrentBoardId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()); + saveCurrentBoardId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()); binding.toolbar.setTitle(board.getTitle()); @@ -585,11 +583,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener binding.emptyContentViewStacks.setVisibility(View.GONE); currentBoardHasStacks = true; } + listMenu.findItem(R.id.archive_cards).setVisible(currentBoardHasStacks); int stackPositionInAdapter = 0; stackAdapter.setStacks(fullStacks); - long currentStackId = Application.readCurrentStackId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()); + long currentStackId = readCurrentStackId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()); for (int i = 0; i < currentBoardStacksCount; i++) { if (fullStacks.get(i).getLocalId() == currentStackId || currentStackId == NO_STACK_ID) { stackPositionInAdapter = i; @@ -796,7 +795,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener if (response.getDeckVersion().isSupported(getApplicationContext())) { runOnUiThread(() -> { syncManager = importSyncManager; - mainViewModel.setCurrentAccount(account, account.getServerDeckVersionAsObject().isSupported(MainActivity.this)); + mainViewModel.setCurrentAccount(account); final Snackbar importSnackbar = BrandedSnackbar.make(binding.coordinatorLayout, R.string.account_is_getting_imported, Snackbar.LENGTH_INDEFINITE); importSnackbar.show(); @@ -942,6 +941,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener } else if (this.boardsList.size() > 1) { // Select second board after deletion setCurrentBoard(this.boardsList.get(1)); } else { // No other board is available, open create dialog + clearBrandColors(this); clearCurrentBoard(); EditBoardDialogFragment.newInstance().show(getSupportFragmentManager(), addBoard); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java index b9ba588df..e5bd4f482 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java @@ -1,15 +1,18 @@ package it.niedermann.nextcloud.deck.ui; +import android.app.Application; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.Board; @SuppressWarnings("WeakerAccess") -public class MainViewModel extends ViewModel { +public class MainViewModel extends AndroidViewModel { private MutableLiveData<Account> currentAccount = new MutableLiveData<>(); private Board currentBoard; @@ -17,6 +20,10 @@ public class MainViewModel extends ViewModel { private boolean currentAccountIsSupportedVersion = false; + public MainViewModel(@NonNull Application application) { + super(application); + } + public Account getCurrentAccount() { return currentAccount.getValue(); } @@ -25,9 +32,9 @@ public class MainViewModel extends ViewModel { return this.currentAccount; } - public void setCurrentAccount(Account currentAccount, boolean versionIsSupported) { + public void setCurrentAccount(Account currentAccount) { this.currentAccount.setValue(currentAccount); - this.currentAccountIsSupportedVersion = versionIsSupported; + this.currentAccountIsSupportedVersion = currentAccount.getServerDeckVersionAsObject().isSupported(getApplication().getApplicationContext()); } public void setCurrentBoard(Board currentBoard) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java index f3d462c90..b0b0d68ae 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java @@ -1,40 +1,31 @@ package it.niedermann.nextcloud.deck.ui; import android.content.Intent; -import android.graphics.Color; import android.net.Uri; import android.text.TextUtils; import android.view.View; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.UiThread; import androidx.appcompat.app.AppCompatActivity; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ActivityPushNotificationBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; -import it.niedermann.nextcloud.deck.ui.branding.Branded; import it.niedermann.nextcloud.deck.ui.card.EditActivity; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; import static android.graphics.Color.parseColor; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToPrimaryToolbar; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToStatusbar; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; -public class PushNotificationActivity extends AppCompatActivity implements Branded { +public class PushNotificationActivity extends AppCompatActivity { private ActivityPushNotificationBinding binding; - private boolean brandingEnabled; - // Provided by Files app NotificationJob private static final String KEY_SUBJECT = "subject"; private static final String KEY_MESSAGE = "message"; @@ -56,8 +47,6 @@ public class PushNotificationActivity extends AppCompatActivity implements Brand setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); - brandingEnabled = Application.isBrandingEnabled(this); - binding.subject.setText(getIntent().getStringExtra(KEY_SUBJECT)); final String message = getIntent().getStringExtra(KEY_MESSAGE); @@ -82,13 +71,6 @@ public class PushNotificationActivity extends AppCompatActivity implements Brand if (account != null) { SingleAccountHelper.setCurrentAccount(this, account.getName()); final SyncManager syncManager = new SyncManager(this); - try { - if (brandingEnabled) { - applyBrand(parseColor(account.getColor()), parseColor(account.getTextColor())); - } - } catch (Throwable t) { - DeckLog.logError(t); - } DeckLog.verbose("account: " + account); observeOnce(syncManager.getLocalBoardIdByCardRemoteIdAndAccount(cardRemoteId, account), PushNotificationActivity.this, (boardLocalId -> { DeckLog.verbose("BoardLocalId " + boardLocalId); @@ -153,11 +135,6 @@ public class PushNotificationActivity extends AppCompatActivity implements Brand @UiThread private void launchEditActivity(@NonNull Account account, Long boardId, Long cardId) { - try { - Application.saveBrandColors(this, Color.parseColor(account.getColor()), Color.parseColor(account.getTextColor())); - } catch (Throwable t) { - DeckLog.logError(t); - } DeckLog.info("starting " + EditActivity.class.getSimpleName() + " with [" + account + ", " + boardId + ", " + cardId + "]"); startActivity(EditActivity.createEditCardIntent(this, account, boardId, cardId)); finish(); @@ -169,15 +146,6 @@ public class PushNotificationActivity extends AppCompatActivity implements Brand return true; } - @Override - public void applyBrand(@ColorInt int mainColor, @ColorInt int textColor) { - if (brandingEnabled) { - applyBrandToStatusbar(getWindow(), mainColor, textColor); - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor)); - } - } - public void applyBrandToSubmitButton(@NonNull Account account) { try { binding.submit.setBackgroundColor(parseColor(account.getColor())); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java index 943e48bd6..bf3734b72 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java @@ -19,6 +19,8 @@ import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToPrimaryTabLayout; + public class AboutActivity extends BrandedActivity { private static final String BUNDLE_KEY_ACCOUNT = "account"; @@ -82,9 +84,8 @@ public class AboutActivity extends BrandedActivity { } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - applyBrandToPrimaryTabLayout(mainColor, textColor, binding.tabLayout); + public void applyBrand(int mainColor) { + applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout); } @NonNull diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java index f33b30ea3..c00ff212a 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java @@ -1,18 +1,26 @@ package it.niedermann.nextcloud.deck.ui.about; import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.FragmentAboutLicenseTabBinding; import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment; +import it.niedermann.nextcloud.deck.util.ColorUtil; +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas; import static it.niedermann.nextcloud.deck.util.SpannableUtil.setTextWithURL; public class AboutFragmentLicenseTab extends BrandedFragment { @@ -29,8 +37,11 @@ public class AboutFragmentLicenseTab extends BrandedFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - binding.aboutAppLicenseButton.setBackgroundColor(mainColor); - binding.aboutAppLicenseButton.setTextColor(textColor); + public void applyBrand(int mainColor) { + @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(requireContext(), R.color.primary)) + ? mainColor + : isDarkTheme(requireContext()) ? Color.WHITE : Color.BLACK; + DrawableCompat.setTintList(binding.aboutAppLicenseButton.getBackground(), ColorStateList.valueOf(finalMainColor)); + binding.aboutAppLicenseButton.setTextColor(ColorUtil.getForegroundColorForBackgroundColor(finalMainColor)); } }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java index bae4720b9..592f2e8cc 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java @@ -53,14 +53,15 @@ public class AccountSwitcherDialog extends BrandedDialogFragment { Glide.with(requireContext()) .load(viewModel.getCurrentAccount().getAvatarUrl(dpToPx(binding.currentAccountItemAvatar.getContext(), R.dimen.avatar_size))) - .error(R.drawable.ic_person_grey600_24dp) + .placeholder(R.drawable.ic_baseline_account_circle_24) + .error(R.drawable.ic_baseline_account_circle_24) .apply(RequestOptions.circleCropTransform()) .into(binding.currentAccountItemAvatar); binding.accountLayout.setOnClickListener((v) -> dismiss()); adapter = new AccountSwitcherAdapter((localAccount -> { - viewModel.setCurrentAccount(localAccount, localAccount.getServerDeckVersionAsObject().isSupported(requireContext())); + viewModel.setCurrentAccount(localAccount); dismiss(); })); @@ -97,7 +98,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { + public void applyBrand(int mainColor) { // applyBrandToLayerDrawable((LayerDrawable) binding.check.getDrawable(), R.id.area, mainColor); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java index 9de4185b1..9c93c422e 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java @@ -31,7 +31,8 @@ public class AccountSwitcherViewHolder extends RecyclerView.ViewHolder { binding.accountHost.setText(Uri.parse(account.getUrl()).getHost()); Glide.with(itemView.getContext()) .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size)))) - .error(R.drawable.ic_person_grey600_24dp) + .placeholder(R.drawable.ic_baseline_account_circle_24) + .error(R.drawable.ic_baseline_account_circle_24) .apply(RequestOptions.circleCropTransform()) .into(binding.accountItemAvatar); itemView.setOnClickListener((v) -> onAccountClick.accept(account)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java index 07ca40661..7c3a2d23c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java @@ -53,7 +53,7 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa setSupportActionBar(binding.toolbar); viewModel = new ViewModelProvider(this).get(MainViewModel.class); - viewModel.setCurrentAccount(account, account.getServerDeckVersionAsObject().isSupported(this)); + viewModel.setCurrentAccount(account); syncManager = new SyncManager(this); adapter = new ArchivedBoardsAdapter(viewModel.isCurrentAccountIsSupportedVersion(), getSupportFragmentManager(), (board) -> syncManager.dearchiveBoard(board)); @@ -67,8 +67,8 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); + public void applyBrand(int mainColor) { + // Nothing to do... } @NonNull diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java index 00500ae97..ed2ee7097 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java @@ -65,8 +65,8 @@ public class ArchivedCardsActvitiy extends BrandedActivity { } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); + public void applyBrand(int mainColor) { + // Nothing to do... } @NonNull diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java index 1283695e0..e5d0a482b 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java @@ -15,10 +15,11 @@ import it.niedermann.nextcloud.deck.databinding.DialogTextColorInputBinding; import it.niedermann.nextcloud.deck.model.full.FullBoard; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; import it.niedermann.nextcloud.deck.ui.MainViewModel; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; + public class EditBoardDialogFragment extends BrandedDialogFragment { private DialogTextColorInputBinding binding; @@ -95,7 +96,7 @@ public class EditBoardDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.input); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.input); } }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java index 4ab3ac80b..e5a50d9f4 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java @@ -17,7 +17,6 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemAccessControlBinding; import it.niedermann.nextcloud.deck.databinding.ItemAccessControlOwnerBinding; @@ -25,10 +24,10 @@ import it.niedermann.nextcloud.deck.model.AccessControl; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.enums.DBStatus; import it.niedermann.nextcloud.deck.ui.branding.Branded; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.util.ViewUtil; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Branded { @@ -122,7 +121,7 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View accessControlChangedListener.updateAccessControl(ac); }); - if (Application.isBrandingEnabled(context)) { + if (isBrandingEnabled(context)) { if (hasManagePermission) { brandSwitch(context, acHolder.binding.permissionEdit, mainColor); brandSwitch(context, acHolder.binding.permissionManage, mainColor); @@ -159,9 +158,9 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View } @Override - public void applyBrand(int mainColor, int textColor) { - if (Application.isBrandingEnabled(context)) { - this.mainColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(context, mainColor); + public void applyBrand(int mainColor) { + if (isBrandingEnabled(context)) { + this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor); notifyDataSetChanged(); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java index 9b170aacd..33c0fe5b1 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java @@ -25,7 +25,6 @@ import it.niedermann.nextcloud.deck.model.full.FullBoard; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData; import it.niedermann.nextcloud.deck.ui.MainViewModel; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar; @@ -34,6 +33,7 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; import static it.niedermann.nextcloud.deck.ui.board.accesscontrol.AccessControlAdapter.HEADER_ITEM_LOCAL_ID; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; public class AccessControlDialogFragment extends BrandedDialogFragment implements AccessControlChangedListener, OnItemClickListener { @@ -135,9 +135,9 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.people); - this.adapter.applyBrand(mainColor, textColor); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.people); + this.adapter.applyBrand(mainColor); } public static DialogFragment newInstance(long boardLocalId) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java index 7570510fd..d460d1590 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java @@ -11,10 +11,11 @@ import androidx.fragment.app.DialogFragment; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogTextColorInputBinding; import it.niedermann.nextcloud.deck.model.Label; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; + public class EditLabelDialogFragment extends BrandedDialogFragment { private DialogTextColorInputBinding binding; @@ -83,7 +84,7 @@ public class EditLabelDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.input); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.input); } }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java index 34c06530c..d8bb57a11 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java @@ -11,12 +11,13 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemManageLabelBinding; import it.niedermann.nextcloud.deck.model.Label; import it.niedermann.nextcloud.deck.ui.branding.Branded; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; + +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; public class ManageLabelsAdapter extends RecyclerView.Adapter<ManageLabelsViewHolder> implements Branded { @@ -74,9 +75,9 @@ public class ManageLabelsAdapter extends RecyclerView.Adapter<ManageLabelsViewHo } @Override - public void applyBrand(int mainColor, int textColor) { - if (Application.isBrandingEnabled(context)) { - this.mainColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(context, mainColor); + public void applyBrand(int mainColor) { + if (isBrandingEnabled(context)) { + this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor); notifyDataSetChanged(); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java index d6727f4b0..3391c7a99 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java @@ -20,12 +20,13 @@ import it.niedermann.nextcloud.deck.model.Label; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData; import it.niedermann.nextcloud.deck.ui.MainViewModel; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDeleteAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB; public class ManageLabelsDialogFragment extends BrandedDialogFragment implements ManageLabelListener, EditLabelListener { @@ -108,9 +109,9 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToFAB(mainColor, textColor, binding.fab); - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.addLabelTitle); + public void applyBrand(int mainColor) { + applyBrandToFAB(mainColor, binding.fab); + applyBrandToEditText(mainColor, binding.addLabelTitle); } public static DialogFragment newInstance(long boardLocalId) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/Branded.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/Branded.java index 15ffb8528..99ad9c074 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/Branded.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/Branded.java @@ -5,5 +5,5 @@ import androidx.annotation.UiThread; public interface Branded { @UiThread - void applyBrand(@ColorInt int mainColor, @ColorInt int textColor); + void applyBrand(@ColorInt int mainColor); }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java index 6278ac1f5..3420e888b 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java @@ -1,158 +1,41 @@ package it.niedermann.nextcloud.deck.ui.branding; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; -import android.view.View; -import android.view.Window; -import android.widget.EditText; import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.core.graphics.drawable.DrawableCompat; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.tabs.TabLayout; - -import it.niedermann.nextcloud.deck.Application; -import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.util.ColorUtil; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.os.Build.VERSION_CODES.M; -import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficient; -import static it.niedermann.nextcloud.deck.util.ColorUtil.isColorDark; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.tintMenuIcon; public abstract class BrandedActivity extends AppCompatActivity implements Branded { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (Application.isBrandingEnabled(this)) { - @ColorInt final int mainColor = Application.readBrandMainColor(this); - @ColorInt final int textColor = Application.readBrandTextColor(this); - setTheme(ColorUtil.isColorDark(textColor) ? R.style.AppThemeLightBrand : R.style.AppTheme); - applyBrandToStatusbar(getWindow(), mainColor, textColor); - } else { - setTheme(R.style.AppTheme); - } - } + @ColorInt + protected int colorAccent; @Override protected void onStart() { super.onStart(); - if (Application.isBrandingEnabled(this)) { - @ColorInt final int mainColor = Application.readBrandMainColor(this); - @ColorInt final int textColor = Application.readBrandTextColor(this); - applyBrand(mainColor, textColor); + final TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.colorAccent, typedValue, true); + colorAccent = typedValue.data; + + if (isBrandingEnabled(this)) { + @ColorInt final int mainColor = readBrandMainColor(this); + applyBrand(mainColor); } } - // TODO maybe this can be handled in R.style.AppThemLightBrand @Override public boolean onCreateOptionsMenu(Menu menu) { - @ColorInt final int textColor = Application.readBrandTextColor(this); for (int i = 0; i < menu.size(); i++) { - Drawable drawable = menu.getItem(i).getIcon(); - if (drawable != null) { - drawable = DrawableCompat.wrap(drawable); - DrawableCompat.setTint(drawable, textColor); - menu.getItem(i).setIcon(drawable); - } + tintMenuIcon(menu.getItem(i), colorAccent); } return super.onCreateOptionsMenu(menu); } - - public static void applyBrandToStatusbar(@NonNull Window window, @ColorInt int mainColor, @ColorInt int textColor) { - if (SDK_INT >= LOLLIPOP) { // Set status bar color - window.setStatusBarColor(mainColor); - if (SDK_INT >= M) { // Set icon and text color of status bar - final View decorView = window.getDecorView(); - if (isColorDark(mainColor)) { - int flags = decorView.getSystemUiVisibility(); - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - decorView.setSystemUiVisibility(flags); - } else { - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); - } - } - } - } - - public static void applyBrandToPrimaryToolbar(@ColorInt int mainColor, @ColorInt int textColor, @NonNull Toolbar toolbar) { - toolbar.setBackgroundColor(mainColor); - toolbar.setTitleTextColor(textColor); - final Drawable overflowDrawable = toolbar.getOverflowIcon(); - if (overflowDrawable != null) { - overflowDrawable.setColorFilter(textColor, PorterDuff.Mode.SRC_ATOP); - toolbar.setOverflowIcon(overflowDrawable); - } - - final Drawable navigationDrawable = toolbar.getNavigationIcon(); - if (navigationDrawable != null) { - navigationDrawable.setColorFilter(textColor, PorterDuff.Mode.SRC_ATOP); - toolbar.setNavigationIcon(navigationDrawable); - } - } - - protected void applyBrandToPrimaryTabLayout(@ColorInt int mainColor, @ColorInt int textColor, @NonNull TabLayout tabLayout) { - tabLayout.setBackgroundColor(mainColor); - tabLayout.setTabTextColors(textColor, textColor); - tabLayout.setTabIconTint(ColorStateList.valueOf(textColor)); - tabLayout.setSelectedTabIndicatorColor(textColor); - } - - public static void applyBrandToFAB(@ColorInt int mainColor, @ColorInt int textColor, @NonNull FloatingActionButton fab) { - fab.setSupportBackgroundTintList(ColorStateList.valueOf(mainColor)); - fab.setColorFilter(textColor); - } - - public static void applyBrandToEditText(@ColorInt int mainColor, @ColorInt int textColor, @NonNull EditText editText) { - @ColorInt final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(editText.getContext(), mainColor); - DrawableCompat.setTintList(editText.getBackground(), new ColorStateList( - new int[][]{ - new int[]{android.R.attr.state_active}, - new int[]{android.R.attr.state_activated}, - new int[]{android.R.attr.state_focused}, - new int[]{android.R.attr.state_pressed}, - new int[]{} - }, - new int[]{ - finalMainColor, - finalMainColor, - finalMainColor, - finalMainColor, - editText.getContext().getResources().getColor(R.color.fg_secondary) - } - )); - } - - /** - * Since we may collide with dark theme in this area, we have to make sure that the color is visible depending on the background - */ - @ColorInt - public static int - getSecondaryForegroundColorDependingOnTheme(@NonNull Context context, @ColorInt int mainColor) { - final boolean isDarkTheme = Application.getAppTheme(context); - if (isDarkTheme && !contrastRatioIsSufficient(mainColor, Color.BLACK)) { - DeckLog.verbose("Contrast ratio between brand color " + String.format("#%06X", (0xFFFFFF & mainColor)) + " and dark theme is too low. Falling back to WHITE as brand color."); - return Color.WHITE; - } else if (!isDarkTheme && !contrastRatioIsSufficient(mainColor, Color.WHITE)) { - DeckLog.verbose("Contrast ratio between brand color " + String.format("#%06X", (0xFFFFFF & mainColor)) + " and light theme is too low. Falling back to BLACK as brand color."); - return Color.BLACK; - } else { - return mainColor; - } - } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java index d502772b3..cfeffe7dc 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java @@ -11,7 +11,8 @@ import androidx.appcompat.app.AlertDialog; import org.jetbrains.annotations.NotNull; -import it.niedermann.nextcloud.deck.Application; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; public class BrandedAlertDialogBuilder extends AlertDialog.Builder implements Branded { @@ -27,23 +28,22 @@ public class BrandedAlertDialogBuilder extends AlertDialog.Builder implements Br this.dialog = super.create(); @NonNull Context context = getContext(); - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); - dialog.setOnShowListener(dialog -> applyBrand(mainColor, textColor)); + @ColorInt final int mainColor = readBrandMainColor(context); + applyBrand(mainColor); + dialog.setOnShowListener(dialog -> applyBrand(mainColor)); return dialog; } @CallSuper @Override - public void applyBrand(int mainColor, int textColor) { + public void applyBrand(int mainColor) { final Button[] buttons = new Button[3]; buttons[0] = dialog.getButton(DialogInterface.BUTTON_POSITIVE); buttons[1] = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); buttons[2] = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); for (Button button : buttons) { if (button != null) { - button.setTextColor(BrandedActivity.getSecondaryForegroundColorDependingOnTheme(button.getContext(), mainColor)); + button.setTextColor(getSecondaryForegroundColorDependingOnTheme(button.getContext(), mainColor)); } } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java index b56ad3669..5bef66f2c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java @@ -10,37 +10,41 @@ import android.view.ViewGroup; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; import java.util.Calendar; -import it.niedermann.nextcloud.deck.Application; +import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.util.ColorUtil; +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; + public class BrandedDatePickerDialog extends DatePickerDialog implements Branded { @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @Nullable Context context = getContext(); if (context != null) { - setThemeDark(Application.getAppTheme(context)); - if (Application.isBrandingEnabled(context)) { - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); + setThemeDark(isDarkTheme(context)); + if (isBrandingEnabled(context)) { + applyBrand(readBrandMainColor(context)); } } return super.onCreateView(inflater, container, savedInstanceState); } @Override - public void applyBrand(int mainColor, int textColor) { - @ColorInt final int buttonTextColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); + public void applyBrand(int mainColor) { + @ColorInt final int buttonTextColor = getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); setOkColor(buttonTextColor); setCancelColor(buttonTextColor); // Text in picker title is always white - setAccentColor(ColorUtil.contrastRatioIsSufficient(Color.WHITE, mainColor) ? mainColor : textColor); + setAccentColor(ColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent)); } /** diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java index 29cac691f..ec3cef553 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java @@ -16,8 +16,8 @@ public class BrandedDeleteAlertDialogBuilder extends BrandedAlertDialogBuilder { @CallSuper @Override - public void applyBrand(int mainColor, int textColor) { - super.applyBrand(mainColor, textColor); + public void applyBrand(int mainColor) { + super.applyBrand(mainColor); final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); if (positiveButton != null) { positiveButton.setTextColor(getContext().getResources().getColor(R.color.danger)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java index 75eb90b12..adf3844e9 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java @@ -2,11 +2,11 @@ package it.niedermann.nextcloud.deck.ui.branding; import android.content.Context; -import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; -import it.niedermann.nextcloud.deck.Application; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; public abstract class BrandedDialogFragment extends DialogFragment implements Branded { @@ -16,10 +16,8 @@ public abstract class BrandedDialogFragment extends DialogFragment implements Br @Nullable Context context = getContext(); if (context != null) { - if (Application.isBrandingEnabled(context)) { - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); + if (isBrandingEnabled(context)) { + applyBrand(readBrandMainColor(context)); } } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java index 0854b547b..fe202f48c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java @@ -2,11 +2,11 @@ package it.niedermann.nextcloud.deck.ui.branding; import android.content.Context; -import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import it.niedermann.nextcloud.deck.Application; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; public abstract class BrandedFragment extends Fragment implements Branded { @@ -15,10 +15,8 @@ public abstract class BrandedFragment extends Fragment implements Branded { super.onStart(); @Nullable Context context = getContext(); - if (context != null && Application.isBrandingEnabled(context)) { - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); + if (context != null && isBrandingEnabled(context)) { + applyBrand(readBrandMainColor(context)); } } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java index dd7f0a037..e01c523c6 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java @@ -10,10 +10,9 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceViewHolder; -import it.niedermann.nextcloud.deck.Application; - -import static it.niedermann.nextcloud.deck.Application.readBrandMainColor; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; public class BrandedPreferenceCategory extends PreferenceCategory { @@ -21,7 +20,7 @@ public class BrandedPreferenceCategory extends PreferenceCategory { public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - if (Application.isBrandingEnabled(getContext())) { + if (isBrandingEnabled(getContext())) { final View v = holder.itemView.findViewById(android.R.id.title); @Nullable final Context context = getContext(); if (context != null && v instanceof TextView) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java index b28b00ac0..20e6f8dc8 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java @@ -3,24 +3,31 @@ package it.niedermann.nextcloud.deck.ui.branding; import android.graphics.Color; import android.view.View; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; -import it.niedermann.nextcloud.deck.Application; +import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.util.ColorUtil; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; + public class BrandedSnackbar { @NonNull public static Snackbar make( @NonNull View view, @NonNull CharSequence text, @BaseTransientBottomBar.Duration int duration) { final Snackbar snackbar = Snackbar.make(view, text, duration); - if (Application.isBrandingEnabled(view.getContext())) { - int color = Application.readBrandMainColor(view.getContext()); + if (isBrandingEnabled(view.getContext())) { + @ColorInt final int color = readBrandMainColor(view.getContext()); snackbar.setActionTextColor(ColorUtil.isColorDark(color) ? Color.WHITE : color); + } else { + snackbar.setActionTextColor(ContextCompat.getColor(view.getContext(), R.color.primary)); } return snackbar; } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java index a0b2c222e..463800a0a 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java @@ -13,21 +13,16 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.R; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; public class BrandedSwitchPreference extends SwitchPreference implements Branded { @ColorInt private Integer mainColor = null; - @ColorInt - private Integer textColor = null; - @Nullable private Switch switchView; @@ -51,26 +46,25 @@ public class BrandedSwitchPreference extends SwitchPreference implements Branded public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - if (Application.isBrandingEnabled(getContext()) && holder.itemView instanceof ViewGroup) { + if (isBrandingEnabled(getContext()) && holder.itemView instanceof ViewGroup) { switchView = findSwitchWidget(holder.itemView); - if (mainColor != null && textColor != null) { + if (mainColor != null) { applyBrand(); } } } @Override - public void applyBrand(@ColorInt int mainColor, @ColorInt int textColor) { + public void applyBrand(@ColorInt int mainColor) { this.mainColor = mainColor; - this.textColor = textColor; // onBindViewHolder is called after applyBrand, therefore we have to store the given values and apply them later. - if (Application.isBrandingEnabled(getContext())) { + if (isBrandingEnabled(getContext())) { applyBrand(); } } private void applyBrand() { - if (switchView != null && SDK_INT >= JELLY_BEAN) { + if (switchView != null) { final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(getContext(), mainColor); // int trackColor = Color.argb(77, Color.red(finalMainColor), Color.green(finalMainColor), Color.blue(finalMainColor)); DrawableCompat.setTintList(switchView.getThumbDrawable(), new ColorStateList( diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java index cd67d7820..a1963aa18 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java @@ -10,37 +10,41 @@ import android.view.ViewGroup; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; import java.util.Calendar; -import it.niedermann.nextcloud.deck.Application; +import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.util.ColorUtil; +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; + public class BrandedTimePickerDialog extends TimePickerDialog implements Branded { @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @Nullable Context context = getContext(); if (context != null) { - setThemeDark(Application.getAppTheme(context)); - if (Application.isBrandingEnabled(context)) { - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); + setThemeDark(isDarkTheme(context)); + if (isBrandingEnabled(context)) { + applyBrand(readBrandMainColor(context)); } } return super.onCreateView(inflater, container, savedInstanceState); } @Override - public void applyBrand(int mainColor, int textColor) { - @ColorInt final int buttonTextColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); + public void applyBrand(int mainColor) { + @ColorInt final int buttonTextColor = getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); setOkColor(buttonTextColor); setCancelColor(buttonTextColor); // Text in picker title is always white - setAccentColor(ColorUtil.contrastRatioIsSufficient(Color.WHITE, mainColor) ? mainColor : textColor); + setAccentColor(ColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent)); } /** diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java new file mode 100644 index 000000000..02ad6b309 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java @@ -0,0 +1,128 @@ +package it.niedermann.nextcloud.deck.ui.branding; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.view.MenuItem; +import android.widget.EditText; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.preference.PreferenceManager; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.tabs.TabLayout; + +import it.niedermann.nextcloud.deck.DeckLog; +import it.niedermann.nextcloud.deck.R; + +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficient; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas; +import static it.niedermann.nextcloud.deck.util.ColorUtil.getContrastRatio; +import static it.niedermann.nextcloud.deck.util.ColorUtil.getForegroundColorForBackgroundColor; + +public abstract class BrandingUtil { + + private BrandingUtil() { + // Util class + } + + public static boolean isBrandingEnabled(@NonNull Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getBoolean(context.getString(R.string.pref_key_branding), true); + } + + @ColorInt + public static int readBrandMainColor(@NonNull Context context) { + if (isBrandingEnabled(context)) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + DeckLog.log("--- Read: shared_preference_theme_main"); + return sharedPreferences.getInt(context.getString(R.string.shared_preference_theme_main), context.getApplicationContext().getResources().getColor(R.color.defaultBrand)); + } else { + return context.getResources().getColor(R.color.defaultBrand); + } + } + + public static void saveBrandColors(@NonNull Context context, @ColorInt int mainColor) { + if (isBrandingEnabled(context) && context instanceof BrandedActivity) { + final BrandedActivity activity = (BrandedActivity) context; + activity.applyBrand(mainColor); + } + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + DeckLog.log("--- Write: shared_preference_theme_main" + " | " + mainColor); + editor.putInt(context.getString(R.string.shared_preference_theme_main), mainColor); + editor.apply(); + } + + public static void clearBrandColors(@NonNull Context context) { + if (isBrandingEnabled(context) && context instanceof BrandedActivity) { + final BrandedActivity activity = (BrandedActivity) context; + activity.applyBrand(ContextCompat.getColor(context, R.color.defaultBrand)); + } + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + DeckLog.log("--- Write: Remove: shared_preference_theme_main" + " | "); + editor.remove(context.getString(R.string.shared_preference_theme_main)); + editor.apply(); + } + + /** + * Since we may collide with dark theme in this area, we have to make sure that the color is visible depending on the background + */ + @ColorInt + public static int getSecondaryForegroundColorDependingOnTheme(@NonNull Context context, @ColorInt int mainColor) { + if (contrastRatioIsSufficient(mainColor, ContextCompat.getColor(context, R.color.primary))) { + return mainColor; + } + DeckLog.verbose("Contrast ratio between brand color " + String.format("#%06X", (0xFFFFFF & mainColor)) + " and primary theme background is too low. Falling back to WHITE/BLACK as brand color."); + return isDarkTheme(context) ? Color.WHITE : Color.BLACK; + } + + public static void applyBrandToFAB(@ColorInt int mainColor, @NonNull FloatingActionButton fab) { + final boolean contrastRatioIsSufficient = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(fab.getContext(), R.color.primary)); + fab.setSupportBackgroundTintList(ColorStateList.valueOf(contrastRatioIsSufficient + ? mainColor + : ContextCompat.getColor(fab.getContext(), R.color.accent))); + fab.setColorFilter(contrastRatioIsSufficient ? getForegroundColorForBackgroundColor(mainColor) : mainColor); + } + + public static void applyBrandToPrimaryTabLayout(@ColorInt int mainColor, @NonNull TabLayout tabLayout) { + @ColorInt int finalMainColor = getSecondaryForegroundColorDependingOnTheme(tabLayout.getContext(), mainColor); + tabLayout.setBackgroundColor(ContextCompat.getColor(tabLayout.getContext(), R.color.primary)); + final boolean contrastRatioIsSufficient = getContrastRatio(mainColor, ContextCompat.getColor(tabLayout.getContext(), R.color.primary)) > 1.7d; + tabLayout.setSelectedTabIndicatorColor(contrastRatioIsSufficient ? mainColor : finalMainColor); + } + + public static void applyBrandToEditText(@ColorInt int mainColor, @NonNull EditText editText) { + @ColorInt final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(editText.getContext(), mainColor); + DrawableCompat.setTintList(editText.getBackground(), new ColorStateList( + new int[][]{ + new int[]{android.R.attr.state_active}, + new int[]{android.R.attr.state_activated}, + new int[]{android.R.attr.state_focused}, + new int[]{android.R.attr.state_pressed}, + new int[]{} + }, + new int[]{ + finalMainColor, + finalMainColor, + finalMainColor, + finalMainColor, + editText.getContext().getResources().getColor(R.color.fg_secondary) + } + )); + } + + public static void tintMenuIcon(@NonNull MenuItem menuItem, @ColorInt int color) { + Drawable drawable = menuItem.getIcon(); + if (drawable != null) { + drawable = DrawableCompat.wrap(drawable); + DrawableCompat.setTint(drawable, color); + menuItem.setIcon(drawable); + } + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java index b618c16a9..986a66cad 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java @@ -44,13 +44,13 @@ import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData; import it.niedermann.nextcloud.deck.ui.branding.Branded; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import it.niedermann.nextcloud.deck.util.DateUtil; import it.niedermann.nextcloud.deck.util.ViewUtil; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN; public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implements DragAndDropAdapter<FullCard>, Branded { @@ -346,8 +346,8 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem } @Override - public void applyBrand(int mainColor, int textColor) { - this.mainColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(context, mainColor); + public void applyBrand(int mainColor) { + this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor); notifyDataSetChanged(); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java index c039b2952..0710b7d7c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java @@ -2,7 +2,7 @@ package it.niedermann.nextcloud.deck.ui.card; import android.content.Context; import android.content.Intent; -import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Editable; import android.text.InputFilter; @@ -15,11 +15,13 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.drawable.DrawableCompat; import androidx.lifecycle.ViewModelProvider; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; +import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ActivityEditBinding; import it.niedermann.nextcloud.deck.model.Account; @@ -31,6 +33,8 @@ import it.niedermann.nextcloud.deck.util.CardUtil; import static android.graphics.Color.parseColor; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToPrimaryTabLayout; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; public class EditActivity extends BrandedActivity { private static final String BUNDLE_KEY_ACCOUNT = "account"; @@ -87,9 +91,9 @@ public class EditActivity extends BrandedActivity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + applyBrand(colorAccent); setIntent(intent); loadDataFromIntent(); - applyBrand(parseColor(viewModel.getAccount().getColor()), parseColor(viewModel.getAccount().getTextColor())); } private void loadDataFromIntent() { @@ -117,6 +121,7 @@ public class EditActivity extends BrandedActivity { final long boardId = args.getLong(BUNDLE_KEY_BOARD_ID); observeOnce(syncManager.getFullBoardById(account.getId(), boardId), EditActivity.this, (fullBoard -> { + applyBrand(parseColor('#' + fullBoard.getBoard().getColor())); viewModel.setCanEdit(fullBoard.getBoard().isPermissionEdit()); invalidateOptionsMenu(); if (viewModel.isCreateMode()) { @@ -290,12 +295,16 @@ public class EditActivity extends BrandedActivity { } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - applyBrandToPrimaryTabLayout(mainColor, textColor, binding.tabLayout); - final int highlightColor = Color.argb(77, Color.red(textColor), Color.green(textColor), Color.blue(textColor)); - binding.title.setHighlightColor(highlightColor); - binding.title.setTextColor(textColor); + public void applyBrand(int mainColor) { + if(isBrandingEnabled(this)) { + final Drawable navigationIcon = binding.toolbar.getNavigationIcon(); + if (navigationIcon == null) { + DeckLog.error("Excpected navigationIcon to be present."); + } else { + DrawableCompat.setTint(binding.toolbar.getNavigationIcon(), colorAccent); + } + applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout); + } } @NonNull diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java index 0880b343b..d2fd4d40d 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java @@ -64,7 +64,7 @@ public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> { : syncManager.findProposalsForUsersToAssignForACL(accountId, boardId, activity.getResources().getInteger(R.integer.max_users_suggested)); } else { liveData = constraintLength > 0 - ? syncManager.searchUserByUidOrDisplayName(accountId, cardId, constraint.toString()) + ? syncManager.searchUserByUidOrDisplayName(accountId, boardId, cardId, constraint.toString()) : syncManager.findProposalsForUsersToAssign(accountId, boardId, cardId, activity.getResources().getInteger(R.integer.max_users_suggested)); } observeOnce(liveData, activity, users -> { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java index 3ecee8583..a72ed8ef2 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java @@ -35,8 +35,6 @@ import it.niedermann.nextcloud.deck.util.DateUtil; import it.niedermann.nextcloud.deck.util.MimeTypeUtil; import static androidx.recyclerview.widget.RecyclerView.NO_ID; -import static it.niedermann.nextcloud.deck.Application.readBrandMainColor; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard; @SuppressWarnings("WeakerAccess") @@ -73,7 +71,6 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo this.attachmentClickedListener = attachmentClickedListener; this.account = account; this.cardLocalId = cardLocalId == null ? NO_ID : cardLocalId; - this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, readBrandMainColor(context)); setHasStableIds(true); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java index cab648c6b..95cbb3d5e 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java @@ -56,7 +56,7 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import static android.app.Activity.RESULT_OK; import static androidx.core.content.PermissionChecker.checkSelfPermission; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToFAB; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB; import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_DEFAULT; import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_IMAGE; import static it.niedermann.nextcloud.deck.util.AttachmentUtil.copyContentUriToTempFile; @@ -364,8 +364,8 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToFAB(mainColor, textColor, binding.fab); + public void applyBrand(int mainColor) { + applyBrandToFAB(mainColor, binding.fab); } public static Fragment newInstance() { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java index 5e9324a3c..169a37c79 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java @@ -16,8 +16,8 @@ import it.niedermann.nextcloud.deck.databinding.ItemCommentBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment; -import static it.niedermann.nextcloud.deck.Application.readBrandMainColor; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; public class CardCommentsAdapter extends RecyclerView.Adapter<ItemCommentViewHolder> { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsEditDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsEditDialogFragment.java index f43ccb5a0..692f8eb8f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsEditDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsEditDialogFragment.java @@ -16,10 +16,11 @@ import java.util.Objects; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogAddCommentBinding; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; + public class CardCommentsEditDialogFragment extends BrandedDialogFragment { private static final String BUNDLE_KEY_COMMENT_ID = "commentId"; private static final String BUNDLE_KEY_COMMENT_MESSAGE = "commentMessage"; @@ -78,8 +79,8 @@ public class CardCommentsEditDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.input); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.input); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java index 302173544..e261c37a2 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java @@ -29,8 +29,8 @@ import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel; import static android.view.View.GONE; import static android.view.View.VISIBLE; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToEditText; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToFAB; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB; import static it.niedermann.nextcloud.deck.util.ViewUtil.setupMentions; public class CardCommentsFragment extends BrandedFragment implements CommentEditedListener, CommentDeletedListener, CommentSelectAsReplyListener { @@ -142,9 +142,9 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToEditText(mainColor, textColor, binding.message); - applyBrandToFAB(mainColor, textColor, binding.fab); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.message); + applyBrandToFAB(mainColor, binding.fab); } @Override diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java index a2a01b618..3182fffa2 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java @@ -58,7 +58,7 @@ import it.niedermann.nextcloud.deck.util.ViewUtil; import static android.text.format.DateFormat.getDateFormat; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToEditText; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx; public class CardDetailsFragment extends BrandedFragment implements OnDateSetListener, OnTimeSetListener { @@ -136,12 +136,12 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToEditText(mainColor, textColor, binding.labels); - applyBrandToEditText(mainColor, textColor, binding.dueDateDate); - applyBrandToEditText(mainColor, textColor, binding.dueDateTime); - applyBrandToEditText(mainColor, textColor, binding.people); - applyBrandToEditText(mainColor, textColor, binding.description); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.labels); + applyBrandToEditText(mainColor, binding.dueDateDate); + applyBrandToEditText(mainColor, binding.dueDateTime); + applyBrandToEditText(mainColor, binding.people); + applyBrandToEditText(mainColor, binding.description); } private void setupDescription() { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java index 3ba3eb59f..9eef878c3 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ActivityExceptionBinding; +import it.niedermann.nextcloud.deck.ui.exception.tips.TipsAdapter; import it.niedermann.nextcloud.deck.util.ExceptionUtil; import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard; @@ -41,6 +42,11 @@ public class ExceptionActivity extends AppCompatActivity { binding.stacktrace.setText(debugInfo); + final TipsAdapter adapter = new TipsAdapter(this::startActivity); + binding.tips.setAdapter(adapter); + binding.tips.setNestedScrollingEnabled(false); + adapter.setThrowable(this, null, throwable); + binding.copy.setOnClickListener((v) -> copyToClipboard(this, getString(R.string.simple_exception), "```\n" + debugInfo + "\n```")); binding.close.setOnClickListener((v) -> finish()); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java index 9ab9b70c8..6c0d0ba79 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java @@ -2,8 +2,6 @@ package it.niedermann.nextcloud.deck.ui.exception; import android.app.Dialog; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.View; @@ -13,27 +11,13 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatDialogFragment; import androidx.fragment.app.DialogFragment; -import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException; -import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException; -import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; -import com.nextcloud.android.sso.exceptions.TokenMismatchException; - -import org.json.JSONException; - -import java.net.ConnectException; -import java.net.SocketTimeoutException; - -import it.niedermann.nextcloud.deck.BuildConfig; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogExceptionBinding; -import it.niedermann.nextcloud.deck.exceptions.DeckException; -import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.ui.exception.tips.TipsAdapter; import it.niedermann.nextcloud.deck.util.ExceptionUtil; -import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard; public class ExceptionDialogFragment extends AppCompatDialogFragment { @@ -75,69 +59,7 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment { DeckLog.logError(throwable); - if (throwable instanceof TokenMismatchException) { - adapter.add(R.string.error_dialog_tip_token_mismatch_retry); - adapter.add(R.string.error_dialog_tip_token_mismatch_clear_storage); - Intent intent = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID)) - .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_deck_info); - adapter.add(R.string.error_dialog_tip_clear_storage, intent); - } else if (throwable instanceof NextcloudFilesAppNotSupportedException) { - adapter.add(R.string.error_dialog_tip_files_outdated); - } else if (throwable instanceof NextcloudApiNotRespondingException) { - adapter.add(R.string.error_dialog_tip_files_force_stop); - adapter.add(R.string.error_dialog_tip_files_delete_storage); - } else if (throwable instanceof SocketTimeoutException || throwable instanceof ConnectException) { - adapter.add(R.string.error_dialog_timeout_instance); - adapter.add(R.string.error_dialog_timeout_toggle, new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS).putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_network)); - } else if (throwable instanceof JSONException || throwable instanceof NullPointerException) { - adapter.add(R.string.error_dialog_check_server); - } else if (throwable instanceof NextcloudHttpRequestFailedException) { - int statusCode = ((NextcloudHttpRequestFailedException) throwable).getStatusCode(); - switch (statusCode) { - case 302: - adapter.add(R.string.error_dialog_redirect); - break; - case 500: - if (account != null) { - adapter.add(R.string.error_dialog_check_server_logs, new Intent(Intent.ACTION_VIEW) - .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_server_logs) - .setData(Uri.parse(account.getUrl() + getString(R.string.url_fragment_server_logs)))); - } else { - adapter.add(R.string.error_dialog_check_server_logs); - } - break; - case 503: - adapter.add(R.string.error_dialog_check_maintenance); - break; - case 507: - adapter.add(R.string.error_dialog_insufficient_storage); - break; - } - } else if (throwable instanceof UploadAttachmentFailedException) { - adapter.add(R.string.error_dialog_attachment_upload_failed); - } else if (throwable instanceof DeckException) { - switch (((DeckException) throwable).getHint()) { - case CAPABILITIES_VERSION_NOT_PARSABLE: - if (account != null) { - adapter.add(R.string.error_dialog_version_not_parsable, new Intent(Intent.ACTION_VIEW) - .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_install) - .setData(Uri.parse(account.getUrl() + getString(R.string.url_fragment_install_deck)))); - } else { - adapter.add(R.string.error_dialog_version_not_parsable); - } - break; - case CAPABILITIES_NOT_PARSABLE: - default: - if (account != null) { - adapter.add(R.string.error_dialog_capabilities_not_parsable, new Intent(Intent.ACTION_VIEW) - .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_server_logs) - .setData(Uri.parse(account.getUrl() + getString(R.string.url_fragment_server_logs)))); - } else { - adapter.add(R.string.error_dialog_capabilities_not_parsable); - } - } - } + adapter.setThrowable(requireContext(), account, throwable); return new AlertDialog.Builder(requireActivity()) .setView(binding.getRoot()) diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java index 8ca1a4e09..a059b2956 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java @@ -1,6 +1,11 @@ package it.niedermann.nextcloud.deck.ui.exception.tips; +import android.content.Context; import android.content.Intent; +import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,10 +16,26 @@ import androidx.annotation.StringRes; import androidx.core.util.Consumer; import androidx.recyclerview.widget.RecyclerView; +import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException; +import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; +import com.nextcloud.android.sso.exceptions.TokenMismatchException; + +import org.json.JSONException; + +import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.util.LinkedList; import java.util.List; +import it.niedermann.nextcloud.deck.BuildConfig; import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.exceptions.DeckException; +import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException; +import it.niedermann.nextcloud.deck.model.Account; + +import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; +import static it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment.INTENT_EXTRA_BUTTON_TEXT; public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> { @@ -44,6 +65,87 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> { return tips.size(); } + public void setThrowable(@NonNull Context context, @Nullable Account account, @NonNull Throwable throwable) { + if (throwable instanceof TokenMismatchException) { + add(R.string.error_dialog_tip_token_mismatch_retry); + add(R.string.error_dialog_tip_token_mismatch_clear_storage); + Intent intent = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID)) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_deck_info); + add(R.string.error_dialog_tip_clear_storage, intent); + } else if (throwable instanceof NextcloudFilesAppNotSupportedException) { + add(R.string.error_dialog_tip_files_outdated); + } else if (throwable instanceof NextcloudApiNotRespondingException) { + if (VERSION.SDK_INT >= VERSION_CODES.M) { + add(R.string.error_dialog_tip_disable_battery_optimizations, new Intent().setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_battery_settings)); + } else { + add(R.string.error_dialog_tip_disable_battery_optimizations); + } + add(R.string.error_dialog_tip_files_force_stop); + add(R.string.error_dialog_tip_files_delete_storage); + } else if (throwable instanceof SocketTimeoutException || throwable instanceof ConnectException) { + add(R.string.error_dialog_timeout_instance); + add(R.string.error_dialog_timeout_toggle, new Intent(Settings.ACTION_WIFI_SETTINGS).putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_network)); + } else if (throwable instanceof JSONException || throwable instanceof NullPointerException) { + add(R.string.error_dialog_check_server); + } else if (throwable instanceof NextcloudHttpRequestFailedException) { + int statusCode = ((NextcloudHttpRequestFailedException) throwable).getStatusCode(); + switch (statusCode) { + case 302: + add(R.string.error_dialog_redirect); + break; + case 500: + if (account != null) { + add(R.string.error_dialog_check_server_logs, new Intent(Intent.ACTION_VIEW) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_server_logs) + .setData(Uri.parse(account.getUrl() + context.getString(R.string.url_fragment_server_logs)))); + } else { + add(R.string.error_dialog_check_server_logs); + } + break; + case 503: + add(R.string.error_dialog_check_maintenance); + break; + case 507: + add(R.string.error_dialog_insufficient_storage); + break; + } + } else if (throwable instanceof UploadAttachmentFailedException) { + add(R.string.error_dialog_attachment_upload_failed); + } else if (throwable instanceof DeckException) { + switch (((DeckException) throwable).getHint()) { + case CAPABILITIES_VERSION_NOT_PARSABLE: + if (account != null) { + add(R.string.error_dialog_version_not_parsable, new Intent(Intent.ACTION_VIEW) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_install) + .setData(Uri.parse(account.getUrl() + context.getString(R.string.url_fragment_install_deck)))); + } else { + add(R.string.error_dialog_version_not_parsable); + } + break; + case CAPABILITIES_NOT_PARSABLE: + default: + if (account != null) { + add(R.string.error_dialog_capabilities_not_parsable, new Intent(Intent.ACTION_VIEW) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_server_logs) + .setData(Uri.parse(account.getUrl() + context.getString(R.string.url_fragment_server_logs)))); + } else { + add(R.string.error_dialog_capabilities_not_parsable); + } + } + } else if (throwable instanceof RuntimeException) { + if (throwable.getMessage() != null && throwable.getMessage().contains("database")) { + Intent reportIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.url_report_bug))) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_report_issue); + add(R.string.error_dialog_tip_database_upgrade_failed, reportIntent); + Intent clearIntent = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID)) + .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_deck_info); + add(R.string.error_dialog_tip_clear_storage, clearIntent); + } + } + } + public void add(@StringRes int text) { add(text, null); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java index 12dd38eac..aa6f59d04 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java @@ -23,10 +23,11 @@ import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogFilterBinding; import it.niedermann.nextcloud.deck.model.enums.EDueType; import it.niedermann.nextcloud.deck.model.internal.FilterInformation; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; + public class FilterDialogFragment extends BrandedDialogFragment { private DialogFilterBinding binding; @@ -101,8 +102,8 @@ public class FilterDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - @ColorInt int finalMainColor = BrandedActivity.getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); + public void applyBrand(int mainColor) { + @ColorInt int finalMainColor = getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor); binding.tabLayout.setSelectedTabIndicatorColor(finalMainColor); indicator.setColorFilter(finalMainColor, PorterDuff.Mode.SRC_ATOP); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java index 231e3f124..4b43cbed6 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java @@ -34,7 +34,8 @@ public class ManageAccountViewHolder extends RecyclerView.ViewHolder { binding.accountHost.setText(Uri.parse(account.getUrl()).getHost()); Glide.with(itemView.getContext()) .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size)))) - .error(R.drawable.ic_person_grey600_24dp) + .placeholder(R.drawable.ic_baseline_account_circle_24) + .error(R.drawable.ic_baseline_account_circle_24) .apply(RequestOptions.circleCropTransform()) .into(binding.accountItemAvatar); binding.currentAccountIndicator.setSelected(isCurrentAccount); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java index ed62e85cb..9d273cdcb 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java @@ -1,6 +1,5 @@ package it.niedermann.nextcloud.deck.ui.manageaccounts; -import android.graphics.Color; import android.os.Bundle; import android.util.Log; @@ -9,15 +8,15 @@ import androidx.appcompat.app.AppCompatActivity; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.databinding.ActivityManageAccountsBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; -public class ManageAccountsActivity extends BrandedActivity { +public class ManageAccountsActivity extends AppCompatActivity { private static final String TAG = ManageAccountsActivity.class.getSimpleName(); @@ -39,8 +38,7 @@ public class ManageAccountsActivity extends BrandedActivity { adapter = new ManageAccountAdapter((account) -> { SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.getName()); syncManager = new SyncManager(this); - Application.saveBrandColors(this, Color.parseColor(account.getColor()), Color.parseColor(account.getTextColor())); - Application.saveCurrentAccountId(this, account.getId()); + saveCurrentAccountId(this, account.getId()); }, (accountPair) -> { if (accountPair.first != null) { syncManager.deleteAccount(accountPair.first.getId()); @@ -50,8 +48,7 @@ public class ManageAccountsActivity extends BrandedActivity { Account newAccount = accountPair.second; if (newAccount != null) { SingleAccountHelper.setCurrentAccount(getApplicationContext(), newAccount.getName()); - Application.saveBrandColors(this, Color.parseColor(newAccount.getColor()), Color.parseColor(newAccount.getTextColor())); - Application.saveCurrentAccountId(this, newAccount.getId()); + saveCurrentAccountId(this, newAccount.getId()); syncManager = new SyncManager(this); } else { Log.i(TAG, "Got delete account request, but new account is null. Maybe last account has been deleted?"); @@ -59,7 +56,7 @@ public class ManageAccountsActivity extends BrandedActivity { }); binding.accounts.setAdapter(adapter); - observeOnce(syncManager.readAccount(Application.readCurrentAccountId(this)), this, (account -> { + observeOnce(syncManager.readAccount(readCurrentAccountId(this)), this, (account -> { adapter.setCurrentAccount(account); syncManager.readAccounts().observe(this, (localAccounts -> { if (localAccounts.size() == 0) { @@ -77,9 +74,4 @@ public class ManageAccountsActivity extends BrandedActivity { public void onBackPressed() { onSupportNavigateUp(); } - - @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java index b2e9cb012..ab0cc4816 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java @@ -2,34 +2,51 @@ package it.niedermann.nextcloud.deck.ui.preparecreate; import android.content.ClipData; import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; import android.os.Bundle; import android.text.TextUtils; import android.widget.ArrayAdapter; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import java.util.List; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.DeckLog; +import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ActivityPrepareCreateBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.Board; import it.niedermann.nextcloud.deck.model.full.FullStack; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; import it.niedermann.nextcloud.deck.ui.ImportAccountActivity; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; +import it.niedermann.nextcloud.deck.ui.branding.Branded; import it.niedermann.nextcloud.deck.ui.card.EditActivity; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; +import it.niedermann.nextcloud.deck.util.ColorUtil; import static android.graphics.Color.parseColor; import static androidx.lifecycle.Transformations.switchMap; - -public class PrepareCreateActivity extends BrandedActivity { +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentBoardId; +import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentStackId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentBoardId; +import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentStackId; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled; +import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas; + +public class PrepareCreateActivity extends AppCompatActivity implements Branded { private ActivityPrepareCreateBinding binding; @@ -59,10 +76,12 @@ public class PrepareCreateActivity extends BrandedActivity { for (Board board : boards) { if (board.getLocalId() == lastBoardId) { binding.boardSelect.setSelection(boardAdapter.getPosition(board)); + applyBrand(Color.parseColor('#' + board.getColor())); break; } } } else { + applyBrand(ContextCompat.getColor(this, R.color.defaultBrand)); binding.boardSelect.setEnabled(false); binding.submit.setEnabled(false); } @@ -97,7 +116,7 @@ public class PrepareCreateActivity extends BrandedActivity { Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); - brandingEnabled = Application.isBrandingEnabled(this); + brandingEnabled = isBrandingEnabled(this); binding = ActivityPrepareCreateBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -127,9 +146,9 @@ public class PrepareCreateActivity extends BrandedActivity { throw new IllegalStateException("hasAccounts() returns true, but readAccounts() returns null or has no entry"); } - lastAccountId = Application.readCurrentAccountId(this); - lastBoardId = Application.readCurrentBoardId(this, lastAccountId); - lastStackId = Application.readCurrentStackId(this, lastAccountId, lastBoardId); + lastAccountId = readCurrentAccountId(this); + lastBoardId = readCurrentBoardId(this, lastAccountId); + lastStackId = readCurrentStackId(this, lastAccountId, lastBoardId); accountAdapter.clear(); accountAdapter.addAll(accounts); @@ -144,12 +163,13 @@ public class PrepareCreateActivity extends BrandedActivity { }); binding.accountSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> { - applyTemporaryBrand(accountAdapter.getItem(position)); updateLiveDataSource(boardsLiveData, boardsObserver, syncManager.getBoardsWithEditPermission(parent.getSelectedItemId())); }); - binding.boardSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> - updateLiveDataSource(stacksLiveData, stacksObserver, syncManager.getStacksForBoard(binding.accountSelect.getSelectedItemId(), parent.getSelectedItemId()))); + binding.boardSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> { + applyBrand(Color.parseColor('#' + ((Board) binding.boardSelect.getSelectedItem()).getColor())); + updateLiveDataSource(stacksLiveData, stacksObserver, syncManager.getStacksForBoard(binding.accountSelect.getSelectedItemId(), parent.getSelectedItemId())); + }); binding.cancel.setOnClickListener((v) -> finish()); binding.submit.setOnClickListener((v) -> onSubmit()); @@ -181,10 +201,10 @@ public class PrepareCreateActivity extends BrandedActivity { startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId, receivedClipData)); } - Application.saveCurrentAccountId(this, account.getId()); - Application.saveCurrentBoardId(this, account.getId(), boardId); - Application.saveCurrentStackId(this, account.getId(), boardId, stackId); - applyBrand(parseColor(account.getColor()), parseColor(account.getTextColor())); + saveCurrentAccountId(this, account.getId()); + saveCurrentBoardId(this, account.getId(), boardId); + saveCurrentStackId(this, account.getId(), boardId, stackId); + applyBrand(parseColor(account.getColor())); finish(); } else { @@ -213,21 +233,19 @@ public class PrepareCreateActivity extends BrandedActivity { return TextUtils.isEmpty(text) ? null : text.toString(); } - private void applyTemporaryBrand(@Nullable Account account) { + @Override + public void applyBrand(int mainColor) { try { - if (account != null && brandingEnabled) { - applyBrand(parseColor(account.getColor()), parseColor(account.getTextColor())); + if (brandingEnabled) { + @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(this, R.color.primary)) + ? mainColor + : isDarkTheme(this) ? Color.WHITE : Color.BLACK; + DrawableCompat.setTintList(binding.submit.getBackground(), ColorStateList.valueOf(finalMainColor)); + binding.submit.setTextColor(ColorUtil.getForegroundColorForBackgroundColor(finalMainColor)); + binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor)); } } catch (Throwable t) { DeckLog.logError(t); } } - - @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); - binding.submit.setBackgroundColor(mainColor); - binding.submit.setTextColor(textColor); - binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor)); - } }
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java index 363e5b46e..6f5a9a7e8 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java @@ -37,7 +37,7 @@ public class SettingsActivity extends BrandedActivity { } @Override - public void applyBrand(int mainColor, int textColor) { - applyBrandToPrimaryToolbar(mainColor, textColor, binding.toolbar); + public void applyBrand(int mainColor) { + // Nothing to do... } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java index f531abbe1..ea8d90f22 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java @@ -4,19 +4,20 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; -import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker; import it.niedermann.nextcloud.deck.ui.branding.Branded; import it.niedermann.nextcloud.deck.ui.branding.BrandedSwitchPreference; +import static it.niedermann.nextcloud.deck.DeckApplication.setAppTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor; + public class SettingsFragment extends PreferenceFragmentCompat implements Branded { private BrandedSwitchPreference wifiOnlyPref; @@ -44,7 +45,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande themePref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { final Boolean darkTheme = (Boolean) newValue; DeckLog.log("darkTheme: " + darkTheme); - Application.setAppTheme(darkTheme); + setAppTheme(darkTheme); requireActivity().setResult(Activity.RESULT_OK); requireActivity().recreate(); return true; @@ -82,16 +83,14 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande super.onStart(); @Nullable Context context = getContext(); if (context != null) { - @ColorInt final int mainColor = Application.readBrandMainColor(context); - @ColorInt final int textColor = Application.readBrandTextColor(context); - applyBrand(mainColor, textColor); + applyBrand(readBrandMainColor(context)); } } @Override - public void applyBrand(int mainColor, int textColor) { - wifiOnlyPref.applyBrand(mainColor, textColor); - themePref.applyBrand(mainColor, textColor); - brandingPref.applyBrand(mainColor, textColor); + public void applyBrand(int mainColor) { + wifiOnlyPref.applyBrand(mainColor); + themePref.applyBrand(mainColor); + brandingPref.applyBrand(mainColor); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java index 12a06bb11..271b60489 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java @@ -21,7 +21,7 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import static android.graphics.PorterDuff.Mode; -import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.getSecondaryForegroundColorDependingOnTheme; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme; import static it.niedermann.nextcloud.deck.util.ExceptionUtil.getDebugInfos; public class ShareProgressDialogFragment extends BrandedDialogFragment { @@ -114,7 +114,7 @@ public class ShareProgressDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { + public void applyBrand(int mainColor) { binding.progress.getProgressDrawable().setColorFilter( getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor), Mode.SRC_IN); binding.errorReportButton.setTextColor(getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/EditStackDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/EditStackDialogFragment.java index 92272b99e..6bedd0a0f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/EditStackDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/EditStackDialogFragment.java @@ -17,11 +17,11 @@ import java.util.Objects; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogStackCreateBinding; -import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; -import static it.niedermann.nextcloud.deck.Application.NO_STACK_ID; +import static it.niedermann.nextcloud.deck.DeckApplication.NO_STACK_ID; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText; public class EditStackDialogFragment extends BrandedDialogFragment { private static final String KEY_STACK_ID = "stack_id"; @@ -90,7 +90,7 @@ public class EditStackDialogFragment extends BrandedDialogFragment { } @Override - public void applyBrand(int mainColor, int textColor) { - BrandedActivity.applyBrandToEditText(mainColor, textColor, binding.input); + public void applyBrand(int mainColor) { + applyBrandToEditText(mainColor, binding.input); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java index 942310199..726a74184 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java @@ -138,9 +138,9 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car } @Override - public void applyBrand(int mainColor, int textColor) { + public void applyBrand(int mainColor) { if (this.adapter != null) { - this.adapter.applyBrand(mainColor, textColor); + this.adapter.applyBrand(mainColor); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java index d444e075e..30dc0ada4 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java @@ -5,21 +5,30 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.Nullable; +import com.google.android.flexbox.FlexboxLayout; import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.WidgetColorChooserBinding; import it.niedermann.nextcloud.deck.util.ViewUtil; +import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx; + public class ColorChooser extends LinearLayout { private WidgetColorChooserBinding binding; + private final FlexboxLayout.LayoutParams params = new FlexboxLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + private Context context; private String[] colors; @@ -32,6 +41,9 @@ public class ColorChooser extends LinearLayout { super(context, attrs); this.context = context; + params.setMargins(0, dpToPx(context, R.dimen.spacer_1x), 0, 0); + params.setFlexBasisPercent(.15f); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorChooser, 0, 0); colors = getResources().getStringArray(a.getResourceId(R.styleable.ColorChooser_colors, 0)); @@ -40,6 +52,7 @@ public class ColorChooser extends LinearLayout { binding = WidgetColorChooserBinding.inflate(LayoutInflater.from(context), this, true); for (final String color : colors) { ImageView image = new ImageView(getContext()); + image.setLayoutParams(params); image.setOnClickListener((imageView) -> { if (previouslySelectedImageView != null) { // null when first selection previouslySelectedImageView.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_grey600_36dp, previouslySelectedColor)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java index 63be42465..501d33106 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java @@ -48,7 +48,7 @@ public class OverlappingAvatars extends RelativeLayout { avatarSize = dpToPx(context, R.dimen.avatar_size_small) + avatarBorderSize * 2; overlapPx = dpToPx(context, R.dimen.avatar_size_small_overlapping); borderDrawable = getResources().getDrawable(R.drawable.avatar_border); - DrawableCompat.setTint(borderDrawable, getResources().getColor(R.color.avatars_overlapping_border_color)); + DrawableCompat.setTint(borderDrawable, getResources().getColor(R.color.bg_card)); } public void setAvatars(@NonNull Account account, @NonNull List<User> assignedUsers) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java index cda805450..8c1fbe1fa 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java @@ -6,17 +6,22 @@ import android.os.Bundle; import android.view.Menu; import android.view.View; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.model.Board; import it.niedermann.nextcloud.deck.model.full.FullCard; import it.niedermann.nextcloud.deck.ui.MainActivity; +import it.niedermann.nextcloud.deck.ui.branding.BrandingUtil; import it.niedermann.nextcloud.deck.ui.card.SelectCardListener; +import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.saveBrandColors; + public class SelectCardForWidgetActivity extends MainActivity implements SelectCardListener { private int appWidgetId; + @ColorInt private int originalBrandColor; @Override protected void onCreate(Bundle savedInstanceState) { @@ -35,6 +40,7 @@ public class SelectCardForWidgetActivity extends MainActivity implements SelectC if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); } + originalBrandColor = BrandingUtil.readBrandMainColor(this); } @Override @@ -45,6 +51,7 @@ public class SelectCardForWidgetActivity extends MainActivity implements SelectC .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); setResult(RESULT_OK, updateIntent); getApplicationContext().sendBroadcast(updateIntent); + saveBrandColors(this, originalBrandColor); finish(); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java index 4adf43b34..08354c5fb 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java @@ -109,7 +109,18 @@ public final class ColorUtil { return ret; } - private static double getContrastRatio(@ColorInt int colorOne, @ColorInt int colorTwo) { + public static boolean contrastRatioIsSufficientBigAreas(@ColorInt int colorOne, @ColorInt int colorTwo) { + ColorPair key = new ColorPair(colorOne, colorTwo); + Boolean ret = CONTRAST_RATIO_SUFFICIENT_CACHE.get(key); + if (ret == null) { + ret = getContrastRatio(colorOne, colorTwo) > 1.47d; + CONTRAST_RATIO_SUFFICIENT_CACHE.put(key, ret); + return ret; + } + return ret; + } + + public static double getContrastRatio(@ColorInt int colorOne, @ColorInt int colorTwo) { final double lum1 = getLuminanace(colorOne); final double lum2 = getLuminanace(colorTwo); final double brightest = Math.max(lum1, lum2); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java index 1039b5c95..6abbde557 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java @@ -27,11 +27,12 @@ import com.bumptech.glide.request.transition.Transition; import java.util.Date; import java.util.List; -import it.niedermann.nextcloud.deck.Application; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.ocs.comment.Mention; +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; + public final class ViewUtil { private ViewUtil() { } @@ -53,7 +54,7 @@ public final class ViewUtil { long diff = DateUtil.getDayDifference(new Date(), dueDate); int backgroundDrawable = 0; - int textColor = Application.getAppTheme(context) ? R.color.dark_fg_primary : R.color.grey600; + int textColor = isDarkTheme(context) ? R.color.dark_fg_primary : R.color.grey600; if (diff == 1) { // due date: tomorrow diff --git a/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml index cc2b6f531..17d6150d8 100644 --- a/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml +++ b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml @@ -1,5 +1,5 @@ <vector android:autoMirrored="true" android:height="24dp" - android:tint="#FFFFFF" android:viewportHeight="24.0" + android:tint="@color/accent" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#FF000000" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> + <path android:fillColor="@color/accent" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> </vector> diff --git a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml index 0945faff9..2cb60dcfb 100644 --- a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml +++ b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml @@ -1,5 +1,5 @@ -<vector android:autoMirrored="true" android:height="24dp" - android:tint="#FFFFFF" android:viewportHeight="24" +<vector android:height="24dp" + android:tint="@color/accent" 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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/> + <path android:fillColor="@color/accent" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/> </vector> diff --git a/app/src/main/res/drawable/ic_check_white_24dp.xml b/app/src/main/res/drawable/ic_check_white_24dp.xml index 65eff3fd3..47dd2131a 100644 --- a/app/src/main/res/drawable/ic_check_white_24dp.xml +++ b/app/src/main/res/drawable/ic_check_white_24dp.xml @@ -1,5 +1,5 @@ <vector android:height="24dp" - android:tint="#FFFFFF" android:viewportHeight="24.0" + android:tint="@android:color/white" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#FF000000" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> </vector> diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml index 8f4d17880..1c878294b 100644 --- a/app/src/main/res/drawable/ic_close_white_24dp.xml +++ b/app/src/main/res/drawable/ic_close_white_24dp.xml @@ -1,5 +1,5 @@ <vector android:autoMirrored="true" android:height="24dp" - android:tint="#FFFFFF" android:viewportHeight="24.0" + android:tint="@color/accent" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> </vector> diff --git a/app/src/main/res/drawable/selected.xml b/app/src/main/res/drawable/selected.xml index cb301fd84..f28dd14cd 100644 --- a/app/src/main/res/drawable/selected.xml +++ b/app/src/main/res/drawable/selected.xml @@ -4,7 +4,7 @@ <layer-list> <item> <shape android:shape="oval"> - <solid android:color="@android:color/holo_green_light" /> + <solid android:color="@color/defaultBrand" /> <stroke android:width="1dp" android:color="@android:color/white" /> </shape> </item> diff --git a/app/src/main/res/drawable/splash_screen.xml b/app/src/main/res/drawable/splash_screen.xml index 14234ed34..d22531b3f 100644 --- a/app/src/main/res/drawable/splash_screen.xml +++ b/app/src/main/res/drawable/splash_screen.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@color/primary" /> + <item android:drawable="@color/defaultBrand" /> <item> <bitmap diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 739c9c33f..67c7a900c 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <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:layout_width="match_parent" android:layout_height="match_parent" - xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> <com.google.android.material.appbar.AppBarLayout @@ -15,15 +15,16 @@ android:layout_width="match_parent" android:layout_height="?android:actionBarSize" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" - app:titleTextColor="@android:color/white" tools:title="@string/about" /> <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:theme="@style/ThemeOverlay.AppCompat.Dark" - app:tabIndicatorColor="@color/accent" /> + app:tabBackground="?attr/colorPrimary" + app:tabIndicatorColor="@color/defaultBrand" + app:tabMode="fixed" + app:tabTextColor="?attr/colorAccent" /> </com.google.android.material.appbar.AppBarLayout> <androidx.viewpager2.widget.ViewPager2 diff --git a/app/src/main/res/layout/activity_archived.xml b/app/src/main/res/layout/activity_archived.xml index fd03521fd..6dd7024dc 100644 --- a/app/src/main/res/layout/activity_archived.xml +++ b/app/src/main/res/layout/activity_archived.xml @@ -15,7 +15,6 @@ android:layout_width="match_parent" android:layout_height="?android:actionBarSize" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" - app:titleTextColor="@android:color/white" tools:title="@string/archived_cards" /> </com.google.android.material.appbar.AppBarLayout> diff --git a/app/src/main/res/layout/activity_attachments.xml b/app/src/main/res/layout/activity_attachments.xml index 2e9f68414..5a820f9d3 100644 --- a/app/src/main/res/layout/activity_attachments.xml +++ b/app/src/main/res/layout/activity_attachments.xml @@ -16,7 +16,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" - app:titleTextColor="@android:color/white" tools:title="@string/attachments" /> </com.google.android.material.appbar.AppBarLayout> diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml index 16f310fb8..4d59b296f 100644 --- a/app/src/main/res/layout/activity_edit.xml +++ b/app/src/main/res/layout/activity_edit.xml @@ -16,16 +16,15 @@ android:layout_height="wrap_content" app:navigationIcon="@drawable/ic_close_white_24dp"> - <EditText - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:theme="@style/EditTextOnPrimaryBackground" - android:layout_marginEnd="16dp" - android:importantForAutofill="no" - android:inputType="textMultiLine" - android:maxLines="5" - tools:text="@tools:sample/lorem" /> + <EditText + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:importantForAutofill="no" + android:inputType="textMultiLine" + android:maxLines="5" + tools:text="@tools:sample/lorem" /> </androidx.appcompat.widget.Toolbar> @@ -34,10 +33,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark" + app:tabBackground="?attr/colorPrimary" app:tabGravity="center" - app:tabIconTint="@android:color/white" - app:tabIndicatorColor="@color/accent" - app:tabMode="fixed" /> + app:tabIconTint="?attr/colorAccent" + app:tabIndicatorColor="@color/defaultBrand" + app:tabMode="fixed" + app:tabTextColor="?attr/colorAccent" /> </com.google.android.material.appbar.AppBarLayout> <androidx.viewpager2.widget.ViewPager2 diff --git a/app/src/main/res/layout/activity_exception.xml b/app/src/main/res/layout/activity_exception.xml index 782b401e8..881c8fa85 100644 --- a/app/src/main/res/layout/activity_exception.xml +++ b/app/src/main/res/layout/activity_exception.xml @@ -14,7 +14,6 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?android:actionBarSize" - app:titleTextColor="@android:color/white" tools:title="@string/simple_exception" /> </com.google.android.material.appbar.AppBarLayout> @@ -36,23 +35,53 @@ </LinearLayout> - <HorizontalScrollView + <ScrollView android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginStart="@dimen/spacer_2x" - android:layout_marginEnd="@dimen/spacer_2x" - android:layout_weight="1" - android:background="@color/bg_highlighted"> + android:layout_weight="1"> - <TextView - android:id="@+id/stacktrace" - android:layout_width="wrap_content" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="@dimen/spacer_1x" - android:textIsSelectable="true" - android:typeface="monospace" - tools:text="@string/android_get_accounts_permission_not_granted_exception_message" /> - </HorizontalScrollView> + android:orientation="vertical"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/tips" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingStart="@dimen/spacer_2x" + android:paddingEnd="@dimen/spacer_2x" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:itemCount="2" + tools:listitem="@layout/item_tip" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/spacer_2x" + android:text="@string/error_dialog_we_need_info" + android:textAppearance="?attr/textAppearanceBody2" /> + + <HorizontalScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginStart="@dimen/spacer_2x" + android:layout_marginEnd="@dimen/spacer_2x" + android:layout_weight="1" + android:background="@color/bg_highlighted"> + + <TextView + android:id="@+id/stacktrace" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="@dimen/spacer_1x" + android:textIsSelectable="true" + android:typeface="monospace" + tools:text="@string/android_get_accounts_permission_not_granted_exception_message" /> + </HorizontalScrollView> + </LinearLayout> + </ScrollView> <LinearLayout android:layout_width="match_parent" @@ -69,7 +98,8 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacer_1x" android:layout_weight=".5" - android:text="@string/simple_close" /> + android:text="@string/simple_close" + android:textColor="@color/defaultBrand" /> <com.google.android.material.button.MaterialButton android:id="@+id/copy" @@ -78,7 +108,8 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacer_1x" android:layout_weight=".5" - android:text="@string/simple_copy" /> + android:text="@string/simple_copy" + app:backgroundTint="@color/defaultBrand" /> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_import_account.xml b/app/src/main/res/layout/activity_import_account.xml index b4294db7b..088c26426 100644 --- a/app/src/main/res/layout/activity_import_account.xml +++ b/app/src/main/res/layout/activity_import_account.xml @@ -48,7 +48,8 @@ android:paddingTop="24dp" android:paddingEnd="32dp" android:paddingBottom="24dp" - android:text="@string/choose_account" /> + android:text="@string/choose_account" + app:backgroundTint="@color/defaultBrand" /> <TextView android:id="@+id/status" @@ -70,6 +71,7 @@ android:layout_below="@id/add_button" android:layout_centerHorizontal="true" android:layout_marginTop="32dp" + android:indeterminateTint="@color/defaultBrand" android:visibility="gone" /> <Button @@ -81,6 +83,7 @@ android:layout_centerHorizontal="true" android:layout_marginTop="16dp" android:text="@string/simple_update" + android:textColor="@color/defaultBrand" android:visibility="gone" tools:visibility="visible" /> </RelativeLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d3b024d98..a5c926a5e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,6 +5,7 @@ android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/bg_card_wrapper" android:fitsSystemWindows="true"> <androidx.coordinatorlayout.widget.CoordinatorLayout @@ -107,9 +108,6 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" - app:titleTextColor="@android:color/white" tools:title="Deck"> <ImageView @@ -124,18 +122,35 @@ app:srcCompat="@drawable/ic_baseline_account_circle_24" tools:targetApi="o" /> - <ImageView - android:id="@+id/filter" + <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" - android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/simple_filter" - android:padding="12dp" - android:tooltipText="@string/simple_filter" - android:translationX="6dp" - app:srcCompat="@drawable/ic_filter_list_white_24dp" - tools:targetApi="o" /> + android:translationX="6dp"> + + <ImageView + android:id="@+id/filter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/simple_filter" + android:padding="12dp" + android:tint="?attr/colorAccent" + android:tooltipText="@string/simple_filter" + app:srcCompat="@drawable/ic_filter_list_white_24dp" + tools:targetApi="o" /> + + <ImageView + android:id="@+id/filter_indicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_marginEnd="12dp" + android:layout_marginBottom="12dp" + android:contentDescription="@null" + android:src="@drawable/circle_grey600_8dp" + android:tint="@color/defaultBrand" /> + </FrameLayout> <ImageView android:id="@+id/archivedCards" @@ -145,6 +160,7 @@ android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/action_archived_cards" android:padding="12dp" + android:tint="?attr/colorAccent" android:tooltipText="@string/action_archived_cards" android:translationX="12dp" android:visibility="gone" @@ -163,19 +179,20 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + app:tabBackground="?attr/colorPrimary" app:tabGravity="center" - app:tabIndicatorColor="@color/accent" + app:tabIndicatorColor="@color/defaultBrand" app:tabMode="fixed" - app:tabTextColor="@android:color/white" /> + app:tabTextColor="?attr/colorAccent" /> <androidx.appcompat.widget.AppCompatImageButton android:id="@+id/list_menu_button" android:layout_width="48dp" android:layout_height="match_parent" - android:background="@android:color/transparent" + android:background="?attr/colorPrimary" android:contentDescription="@string/add_list" android:foreground="?attr/selectableItemBackgroundBorderless" - android:tint="@android:color/white" + android:tint="?attr/colorAccent" android:tooltipText="@string/manage_list" app:srcCompat="@drawable/ic_menu" tools:ignore="UnusedAttribute" /> @@ -190,7 +207,7 @@ android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:contentDescription="@string/add_card" - app:backgroundTint="@color/primary" + app:backgroundTint="@color/defaultBrand" app:srcCompat="@drawable/ic_add_white_24dp" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> @@ -200,6 +217,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" + android:background="?attr/colorPrimary" android:fitsSystemWindows="false" android:theme="@style/NavigationView" app:headerLayout="@layout/nav_header_main" /> diff --git a/app/src/main/res/layout/activity_manage_accounts.xml b/app/src/main/res/layout/activity_manage_accounts.xml index 4cd084a88..bbf1b75c4 100644 --- a/app/src/main/res/layout/activity_manage_accounts.xml +++ b/app/src/main/res/layout/activity_manage_accounts.xml @@ -14,12 +14,10 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" app:contentInsetStartWithNavigation="0dp" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" app:title="@string/manage_accounts" - app:titleMarginStart="0dp" - app:titleTextColor="@android:color/white" /> + app:titleMarginStart="0dp" /> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/res/layout/activity_prepare_create.xml b/app/src/main/res/layout/activity_prepare_create.xml index f4aff7cb8..1128f04f4 100644 --- a/app/src/main/res/layout/activity_prepare_create.xml +++ b/app/src/main/res/layout/activity_prepare_create.xml @@ -15,8 +15,7 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?android:actionBarSize" - app:title="@string/add_card" - app:titleTextColor="@android:color/white" /> + app:title="@string/add_card" /> </com.google.android.material.appbar.AppBarLayout> <ScrollView @@ -73,7 +72,8 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacer_1x" android:layout_weight=".5" - android:text="@android:string/cancel" /> + android:text="@android:string/cancel" + android:textColor="@color/defaultBrand" /> <com.google.android.material.button.MaterialButton android:id="@+id/submit" @@ -82,6 +82,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacer_1x" android:layout_weight=".5" - android:text="@string/simple_add" /> + android:text="@string/simple_add" + app:backgroundTint="@color/defaultBrand" /> </LinearLayout> </RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_push_notification.xml b/app/src/main/res/layout/activity_push_notification.xml index 6fb41be3a..eb14f38d1 100644 --- a/app/src/main/res/layout/activity_push_notification.xml +++ b/app/src/main/res/layout/activity_push_notification.xml @@ -21,8 +21,7 @@ android:layout_width="match_parent" android:layout_height="?android:actionBarSize" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" - app:title="@string/app_name" - app:titleTextColor="@android:color/white" /> + app:title="@string/app_name" /> </com.google.android.material.appbar.AppBarLayout> <ProgressBar diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index e5c8b0214..07f7a62fe 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -15,8 +15,7 @@ android:layout_width="match_parent" android:layout_height="?android:actionBarSize" app:navigationIcon="@drawable/ic_arrow_back_white_24dp" - app:title="@string/simple_settings" - app:titleTextColor="@android:color/white" /> + app:title="@string/simple_settings" /> </com.google.android.material.appbar.AppBarLayout> </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/dialog_board_manage_labels.xml b/app/src/main/res/layout/dialog_board_manage_labels.xml index 0792d8094..ccd4481bc 100644 --- a/app/src/main/res/layout/dialog_board_manage_labels.xml +++ b/app/src/main/res/layout/dialog_board_manage_labels.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:contentDescription="@string/add_comment" - android:tint="@android:color/white" + app:backgroundTint="@color/defaultBrand" app:fabSize="mini" app:srcCompat="@drawable/ic_send_white_24dp" /> </LinearLayout> diff --git a/app/src/main/res/layout/dialog_filter.xml b/app/src/main/res/layout/dialog_filter.xml index e7d9ebd6a..8741e7e36 100644 --- a/app/src/main/res/layout/dialog_filter.xml +++ b/app/src/main/res/layout/dialog_filter.xml @@ -9,7 +9,10 @@ android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" + app:tabIndicatorColor="@color/defaultBrand" app:tabInlineLabel="true" + app:tabMode="fixed" + app:tabTextColor="?attr/colorAccent" app:tabUnboundedRipple="true" /> <androidx.viewpager2.widget.ViewPager2 diff --git a/app/src/main/res/layout/fragment_about_license_tab.xml b/app/src/main/res/layout/fragment_about_license_tab.xml index aac8e79a0..e6730390b 100644 --- a/app/src/main/res/layout/fragment_about_license_tab.xml +++ b/app/src/main/res/layout/fragment_about_license_tab.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <ScrollView 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"> @@ -26,7 +27,8 @@ style="@style/Widget.MaterialComponents.Button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/about_app_license_button" /> + android:text="@string/about_app_license_button" + app:backgroundTint="@color/defaultBrand" /> <TextView style="?android:attr/listSeparatorTextViewStyle" diff --git a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml index b2e5df74a..5aa23961d 100644 --- a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml +++ b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml @@ -32,7 +32,7 @@ android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:visibility="gone" - app:backgroundTint="@color/primary" + app:backgroundTint="@color/defaultBrand" app:srcCompat="@drawable/ic_file_upload_white_24dp" tools:visibility="visible" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_card_edit_tab_comments.xml b/app/src/main/res/layout/fragment_card_edit_tab_comments.xml index da76e2a50..3350480cb 100644 --- a/app/src/main/res/layout/fragment_card_edit_tab_comments.xml +++ b/app/src/main/res/layout/fragment_card_edit_tab_comments.xml @@ -114,7 +114,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:contentDescription="@string/add_comment" - android:tint="@android:color/white" + app:backgroundTint="@color/defaultBrand" app:fabSize="mini" app:srcCompat="@drawable/ic_send_white_24dp" /> </LinearLayout> diff --git a/app/src/main/res/layout/item_autocomplete_label.xml b/app/src/main/res/layout/item_autocomplete_label.xml index adfa5bd93..49b98db0b 100644 --- a/app/src/main/res/layout/item_autocomplete_label.xml +++ b/app/src/main/res/layout/item_autocomplete_label.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <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:layout_width="match_parent" android:layout_height="wrap_content" @@ -12,11 +13,15 @@ android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacer_1x" + android:layout_marginBottom="@dimen/spacer_1x" android:clickable="false" android:ellipsize="middle" android:focusable="false" + android:padding="@dimen/spacer_1x" + app:ensureMinTouchTargetSize="false" tools:backgroundTint="@color/board_default_color" - tools:text="@tools:sample/lorem" + tools:text="@tools:sample/cities" tools:textColor="@android:color/white" /> </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_card.xml b/app/src/main/res/layout/item_card.xml index a3ae195fc..c0457f5e4 100644 --- a/app/src/main/res/layout/item_card.xml +++ b/app/src/main/res/layout/item_card.xml @@ -9,6 +9,7 @@ android:layout_marginTop="@dimen/spacer_1x" android:layout_marginEnd="@dimen/spacer_2x" android:layout_marginBottom="@dimen/spacer_1x" + app:cardBackgroundColor="@color/bg_card" android:focusable="true"> <LinearLayout @@ -32,6 +33,7 @@ android:layout_marginTop="4sp" android:layout_weight="1" android:textSize="18sp" + android:textColor="?attr/colorAccent" tools:ignore="RtlSymmetry" tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l" /> @@ -146,6 +148,7 @@ android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/label_menu" android:padding="@dimen/spacer_1hx" + android:tint="?attr/colorAccent" app:srcCompat="@drawable/ic_menu" /> </LinearLayout> </LinearLayout> diff --git a/app/src/main/res/layout/item_comment.xml b/app/src/main/res/layout/item_comment.xml index 9769f681d..754e989fe 100644 --- a/app/src/main/res/layout/item_comment.xml +++ b/app/src/main/res/layout/item_comment.xml @@ -22,7 +22,7 @@ android:id="@+id/parentBorder" android:layout_width="2dp" android:layout_height="match_parent" - android:background="@color/primary" /> + android:background="?attr/colorPrimary" /> <TextView android:id="@+id/parent" diff --git a/app/src/main/res/layout/item_manage_label.xml b/app/src/main/res/layout/item_manage_label.xml index fc5c86a07..7bc7a7194 100644 --- a/app/src/main/res/layout/item_manage_label.xml +++ b/app/src/main/res/layout/item_manage_label.xml @@ -17,7 +17,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:layout_marginStart="@dimen/spacer_1hx" + android:layout_marginTop="@dimen/spacer_1x" + android:layout_marginEnd="@dimen/spacer_1hx" + android:layout_marginBottom="@dimen/spacer_1x" android:ellipsize="middle" + app:ensureMinTouchTargetSize="false" tools:text="@tools:sample/cities" /> </LinearLayout> diff --git a/app/src/main/res/layout/item_tip.xml b/app/src/main/res/layout/item_tip.xml index 96df0e431..2177d013c 100644 --- a/app/src/main/res/layout/item_tip.xml +++ b/app/src/main/res/layout/item_tip.xml @@ -27,6 +27,7 @@ android:layout_height="wrap_content" android:layout_marginStart="40dp" android:layout_marginEnd="@dimen/spacer_1x" + android:textColor="@color/defaultBrand" android:visibility="visible" tools:text="@string/error_action_open_deck_info" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml index 6f65fba00..34d7cdc7a 100644 --- a/app/src/main/res/layout/nav_header_main.xml +++ b/app/src/main/res/layout/nav_header_main.xml @@ -4,7 +4,7 @@ android:id="@+id/header_view" android:layout_width="match_parent" android:layout_height="@dimen/drawer_header_height" - android:background="@color/primary"> + android:background="@color/defaultBrand"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/logo" @@ -14,6 +14,7 @@ android:layout_margin="@dimen/spacer_2x" android:gravity="center" android:padding="@dimen/spacer_1hx" + android:tint="@android:color/white" app:srcCompat="@drawable/ic_app_logo" /> <TextView diff --git a/app/src/main/res/layout/widget_color_chooser.xml b/app/src/main/res/layout/widget_color_chooser.xml index 1d3970119..2a99c7a6c 100644 --- a/app/src/main/res/layout/widget_color_chooser.xml +++ b/app/src/main/res/layout/widget_color_chooser.xml @@ -1,49 +1,56 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView 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:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> + android:layout_height="match_parent"> - <com.google.android.flexbox.FlexboxLayout - android:id="@+id/colorPicker" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacer_2x" - app:alignItems="stretch" - app:flexWrap="wrap" - app:justifyContent="space_between"> + android:orientation="vertical"> - <androidx.appcompat.widget.AppCompatImageView - android:id="@+id/customColorChooser" - android:layout_width="wrap_content" + <com.google.android.flexbox.FlexboxLayout + android:id="@+id/colorPicker" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:contentDescription="@string/pick_custom_color" - app:srcCompat="@drawable/circle_alpha_colorize_36dp" - tools:tint="@color/grey600" /> - </com.google.android.flexbox.FlexboxLayout> + android:layout_marginTop="@dimen/spacer_2x" + app:alignItems="stretch" + app:flexWrap="wrap" + app:justifyContent="space_between"> - <com.skydoves.colorpickerview.ColorPickerView - android:id="@+id/customColorPicker" - android:layout_width="200dp" - android:layout_height="200dp" - android:layout_gravity="center" - android:layout_marginTop="10dp" - android:visibility="gone" - app:palette="@drawable/palette" - app:selector="@drawable/wheel" - tools:visibility="visible" /> + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/customColorChooser" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacer_1x" + android:contentDescription="@string/pick_custom_color" + app:layout_flexBasisPercent="15%" + app:srcCompat="@drawable/circle_alpha_colorize_36dp" + tools:tint="@color/grey600" /> + </com.google.android.flexbox.FlexboxLayout> - <com.skydoves.colorpickerview.sliders.BrightnessSlideBar - android:id="@+id/brightnessSlide" - android:layout_width="200dp" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="10dp" - android:visibility="gone" - app:borderColor_BrightnessSlider="@android:color/darker_gray" - app:borderSize_BrightnessSlider="5" - app:selector_BrightnessSlider="@drawable/wheel" - tools:visibility="visible" /> -</LinearLayout>
\ No newline at end of file + <com.skydoves.colorpickerview.ColorPickerView + android:id="@+id/customColorPicker" + android:layout_width="200dp" + android:layout_height="200dp" + android:layout_gravity="center" + android:layout_marginTop="10dp" + android:visibility="gone" + app:palette="@drawable/palette" + app:selector="@drawable/wheel" + tools:visibility="visible" /> + + <com.skydoves.colorpickerview.sliders.BrightnessSlideBar + android:id="@+id/brightnessSlide" + android:layout_width="200dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="10dp" + android:visibility="gone" + app:borderColor_BrightnessSlider="@android:color/darker_gray" + app:borderSize_BrightnessSlider="5" + app:selector_BrightnessSlider="@drawable/wheel" + tools:visibility="visible" /> + </LinearLayout> +</ScrollView>
\ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index cf1e4008e..5d0c33072 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -242,4 +242,5 @@ <string name="manage_accounts">Gestiona els comptes</string> <string name="manage_list">Gestiona la llista</string> <string name="simple_reply">Respon</string> - </resources> + <string name="simple_report">Informar</string> +</resources> diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 702eacb78..25e1429ad 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -247,4 +247,5 @@ <string name="manage_accounts">Spravovat účty</string> <string name="manage_list">Spravovat sloupec</string> <string name="simple_reply">Odpovědět</string> - </resources> + <string name="simple_report">Hlášení</string> +</resources> diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0ab89f115..59d9cd8e1 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -189,4 +189,5 @@ <string name="share_link">Del link</string> <string name="manage_accounts">Administrer konti</string> <string name="simple_reply">Besvar</string> - </resources> + <string name="simple_report">Rapporter</string> +</resources> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 20a631f1a..0f92faa35 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Konten verwalten</string> <string name="manage_list">Liste verwalten</string> <string name="simple_reply">Antworten</string> - </resources> + <string name="error_while_uploading_attachment">Fehler beim Hochladen des Anhangs: %1$s</string> + <string name="append_text_to_description">An Beschreibung anhängen</string> + <string name="add_text_as_comment">Als Kommentar hinzufügen</string> + <string name="progress_count">%1$d von %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d Fehler beim Hochladen</item> + <item quantity="other">%1$d Fehler beim Hochladen</item> + </plurals> + <string name="simple_report">Melden</string> +</resources> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 44b0436f8..3c25559f3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Διαχείριση λογαριασμών</string> <string name="manage_list">Διαχείριση λίστας</string> <string name="simple_reply">Απάντηση</string> - </resources> + <string name="error_while_uploading_attachment">Σφάλμα κατά την μεταφόρτωση του συνημμένου: %1$s</string> + <string name="append_text_to_description">Προσάρτηση στην περιγραφή</string> + <string name="add_text_as_comment">Προσθήκη ως σχόλιο</string> + <string name="progress_count">%1$d από %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d σφάλμα κατά την μεταφόρτωση</item> + <item quantity="other">%1$d σφάλματα κατά την μεταφόρτωση</item> + </plurals> + <string name="simple_report">Αναφορά</string> +</resources> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b6dd9ca22..222ad2b53 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -242,4 +242,5 @@ <string name="manage_accounts">Gestionar cuentas</string> <string name="manage_list">Administrar su lista</string> <string name="simple_reply">Responder</string> - </resources> + <string name="simple_report">Informe</string> +</resources> diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 447ac7ebe..f7e51fc35 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -233,4 +233,5 @@ <string name="share_link">Partekatu esteka</string> <string name="manage_accounts">Kudeatu kontuak</string> <string name="simple_reply">Erantzun</string> - </resources> + <string name="simple_report">Jakinarazi</string> +</resources> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 66bacf5dd..fce1c75ee 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -243,4 +243,9 @@ <string name="manage_accounts">Gérer les comptes</string> <string name="manage_list">Gérer la liste</string> <string name="simple_reply">Répondre</string> - </resources> + <string name="error_while_uploading_attachment">Erreur pendant l\'envoi de la pièce jointe : %1$s</string> + <string name="append_text_to_description">Ajouter à la description</string> + <string name="add_text_as_comment">Ajouter en commentaire</string> + <string name="progress_count">%1$d sur %2$d</string> + <string name="simple_report">Signaler</string> +</resources> diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 01e9db4a6..14cb3f3a4 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Administrar contas</string> <string name="manage_list">Administrar a lista</string> <string name="simple_reply">Responder</string> - </resources> + <string name="error_while_uploading_attachment">Produciuse un erro ao enviar o anexo: %1$s</string> + <string name="append_text_to_description">Anexo á descrición</string> + <string name="add_text_as_comment">Engadir como comentario</string> + <string name="progress_count">%1$d de %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d erros ao enviar.</item> + <item quantity="other">%1$d erros ao enviar.</item> + </plurals> + <string name="simple_report">Informe</string> +</resources> diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index fe5937db2..c5c0d07f6 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -227,4 +227,5 @@ <string name="share_link">קישור שיתוף</string> <string name="manage_accounts">ניהול חשבונות</string> <string name="simple_reply">תגובה</string> - </resources> + <string name="simple_report">דיווח</string> +</resources> diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index cadeead36..fbf4ec485 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -231,4 +231,5 @@ <string name="share_link">Dijeli poveznicu</string> <string name="manage_accounts">Upravljaj računima</string> <string name="simple_reply">Odgovori</string> - </resources> + <string name="simple_report">Prijavi</string> +</resources> diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index e6f85fa20..c7d30faa3 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -240,4 +240,5 @@ <string name="archive_cards">Archív kártyák</string> <string name="manage_accounts">Fiókok kezelése</string> <string name="simple_reply">Válasz</string> - </resources> + <string name="simple_report">Jelentés</string> +</resources> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c21cbaa6e..77d6b4535 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Gestisci account</string> <string name="manage_list">Gestisci elenco</string> <string name="simple_reply">Rispondi</string> - </resources> + <string name="error_while_uploading_attachment">Errore durante il caricamento dell\'allegato: %1$s</string> + <string name="append_text_to_description">Aggiungi alla descrizione</string> + <string name="add_text_as_comment">Aggiungi come commento</string> + <string name="progress_count">%1$d di %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d errore durante il caricamento</item> + <item quantity="other">%1$d errori durante il caricamento</item> + </plurals> + <string name="simple_report">Segnala</string> +</resources> diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index c27a7dd51..425a5ffbf 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -240,4 +240,5 @@ <string name="manage_accounts">アカウント管理</string> <string name="manage_list">リスト管理</string> <string name="simple_reply">返信</string> - </resources> + <string name="simple_report">報告</string> +</resources> diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 5394b62fc..94803a997 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -225,4 +225,5 @@ <string name="share_link">링크 공유</string> <string name="manage_accounts">계정 관리</string> <string name="simple_reply">답장</string> - </resources> + <string name="simple_report">보고</string> +</resources> diff --git a/app/src/main/res/values-night/booleans.xml b/app/src/main/res/values-night/booleans.xml new file mode 100644 index 000000000..41cc6feb9 --- /dev/null +++ b/app/src/main/res/values-night/booleans.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <bool name="isDayMode">false</bool> +</resources> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 06862fe83..fb270e106 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <resources> + <color name="primary">@android:color/black</color> + <color name="accent">@android:color/white</color> <color name="fg_secondary">#666</color> <color name="bg_highlighted">#2a2a2a</color> <color name="bg_info_box">#222222</color> - <color name="avatars_overlapping_border_color">#424242</color> + <color name="bg_card">#121212</color> + <color name="bg_card_wrapper">@color/primary</color> <color name="widget_background">#cc212121</color> <color name="widget_foreground">#ccf5f5f5</color> diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 5a211e01f..ccf551f00 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -239,4 +239,5 @@ <string name="share_link">Delen link</string> <string name="manage_accounts">Accounts beheren </string> <string name="simple_reply">Antwoord</string> - </resources> + <string name="simple_report">Rapporteer</string> +</resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2e8384d1c..4aff11579 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -247,4 +247,15 @@ <string name="manage_accounts">Zarządzaj kontami</string> <string name="manage_list">Zarządzaj listą</string> <string name="simple_reply">Odpowiedz</string> - </resources> + <string name="error_while_uploading_attachment">Błąd podczas przesyłania załącznika: %1$s</string> + <string name="append_text_to_description">Dołącz do opisu</string> + <string name="add_text_as_comment">Dodaj jako komentarz</string> + <string name="progress_count">%1$d z %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d błąd podczas wysyłania.</item> + <item quantity="few">%1$d błędy podczas wysyłania.</item> + <item quantity="many">%1$d błędów podczas wysyłania.</item> + <item quantity="other">%1$d błędów podczas wysyłania.</item> + </plurals> + <string name="simple_report">Raport</string> +</resources> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ba2f5f45b..ed575093f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Gerenciar contas</string> <string name="manage_list">Gerenciar lista</string> <string name="simple_reply">Responder</string> - </resources> + <string name="error_while_uploading_attachment">Erro ao enviar anexo: %1$s</string> + <string name="append_text_to_description">Anexar à descrição</string> + <string name="add_text_as_comment">Adicionar como comentário</string> + <string name="progress_count">%1$d de %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d erros durante o envio.</item> + <item quantity="other">%1$d erros durante o envio.</item> + </plurals> + <string name="simple_report">Reportar</string> +</resources> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c8e6b9094..42c1514f9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -243,4 +243,5 @@ <string name="manage_accounts">Управление аккаунтами</string> <string name="manage_list">Управлять списком</string> <string name="simple_reply">Ответить</string> - </resources> + <string name="simple_report">Сообщить</string> +</resources> diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index b363e4d67..24eee9531 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -247,4 +247,15 @@ <string name="manage_accounts">Spravovať účty</string> <string name="manage_list">Spravovať zoznam</string> <string name="simple_reply">Odpoveď</string> - </resources> + <string name="error_while_uploading_attachment">Chyba pri nahrávaní prílohy: %1$s</string> + <string name="append_text_to_description">Pripojiť k popisu</string> + <string name="add_text_as_comment">Pridať ako komentár</string> + <string name="progress_count">%1$d z %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">%1$d chyba pri nahrávaní</item> + <item quantity="few">%1$d chyby pri nahrávaní</item> + <item quantity="many">%1$d chyby pri nahrávaní</item> + <item quantity="other">%1$d chýb pri nahrávaní</item> + </plurals> + <string name="simple_report">Hlásenie</string> +</resources> diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index fba5d7bb7..56c23a7b6 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -246,4 +246,5 @@ <string name="manage_accounts">Upravljanje z računi</string> <string name="manage_list">Upravljanje seznama</string> <string name="simple_reply">Odgovori</string> - </resources> + <string name="simple_report">Poročilo</string> +</resources> diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index cd1cfc0f0..c5d52389e 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -241,4 +241,5 @@ <string name="share_link">Веза дељења</string> <string name="manage_accounts">Управљање налозима</string> <string name="simple_reply">Одговори</string> - </resources> + <string name="simple_report">Пријави</string> +</resources> diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 83fb4bfc7..5b126e526 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -224,4 +224,5 @@ <string name="archive_cards">Arkivera kort</string> <string name="manage_accounts">Hantera konton</string> <string name="simple_reply">Svara</string> - </resources> + <string name="simple_report">Rapportera</string> +</resources> diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 67aab09bb..e76dbe391 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -243,4 +243,13 @@ <string name="manage_accounts">Hesap yönetimi</string> <string name="manage_list">Liste yönetimi</string> <string name="simple_reply">Yanıtla</string> - </resources> + <string name="error_while_uploading_attachment">Ek Dosyalara kaydedilirken hata: %1$s</string> + <string name="append_text_to_description">Açıklamaya ekle</string> + <string name="add_text_as_comment">Yorum olarak ekle</string> + <string name="progress_count">%1$d / %2$d</string> + <plurals name="progress_error_count"> + <item quantity="one">Yükleme sırasında %1$d sorun çıktı.</item> + <item quantity="other">Yükleme sırasında %1$d sorun çıktı.</item> + </plurals> + <string name="simple_report">Hata bildirin</string> +</resources> diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 947ee1706..024f60d62 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -227,4 +227,5 @@ <string name="share_link">Поширити посилання</string> <string name="manage_accounts">Облікові записи</string> <string name="simple_reply">Відповісти</string> - </resources> + <string name="simple_report">Звіт</string> +</resources> diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml new file mode 100644 index 000000000..88e43bf09 --- /dev/null +++ b/app/src/main/res/values-v23/styles.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="AppTheme" parent="BaseTheme"> + <item name="android:windowLightStatusBar">@bool/isDayMode</item> + <item name="android:statusBarColor">?attr/colorPrimary</item> + </style> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 000000000..15ac19bf3 --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="AppTheme" parent="BaseTheme"> + <item name="android:windowLightStatusBar">@bool/isDayMode</item> + <item name="android:statusBarColor">?attr/colorPrimary</item> + <item name="android:navigationBarColor">?attr/colorPrimary</item> + <item name="android:windowLightNavigationBar">@bool/isDayMode</item> + </style> + + <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> + <item name="android:windowBackground">@drawable/splash_screen</item> + <item name="colorPrimaryDark">@color/defaultBrand</item> + <item name="android:navigationBarColor">@color/defaultBrand</item> + <item name="android:windowLightNavigationBar">true</item> + </style> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ccdbe8154..6e19325ec 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -223,4 +223,5 @@ <string name="share_link">共享链接</string> <string name="manage_accounts">管理账号</string> <string name="simple_reply">回复</string> - </resources> + <string name="simple_report">报告</string> +</resources> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 02124655d..715f8b814 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -8,6 +8,4 @@ <attr name="description" format="string" /> <attr name="image" format="reference" /> </declare-styleable> - <attr name="toolbarEditTextColor" format="reference" /> - <attr name="toolbarEditTextHighlightColor" format="reference" /> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/booleans.xml b/app/src/main/res/values/booleans.xml new file mode 100644 index 000000000..c47017c70 --- /dev/null +++ b/app/src/main/res/values/booleans.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <bool name="isDayMode">true</bool> +</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d295b8fae..6db0c7441 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <color name="primary">#0082c9</color> - <color name="accent">#ffffff</color> + <color name="primary">@android:color/white</color> + <color name="accent">#000000</color> + <color name="defaultBrand">#0082C9</color> <color name="toolbarEditTextHighlightColor">#55ffffff</color> <color name="danger">#d40000</color> <color name="fg_accent">#fff</color> @@ -10,8 +11,8 @@ <color name="bg_highlighted">#eee</color> <color name="grey600">#757575</color> <color name="bg_info_box">#dddddd</color> - - <color name="avatars_overlapping_border_color">#ffffff</color> + <color name="bg_card">@android:color/white</color> + <color name="bg_card_wrapper">#fafafa</color> <color name="dark_fg_primary">#e5e5e5</color> @@ -23,17 +24,20 @@ <color name="overdue_text_color">#FFFFFF</color> <!-- board color picker colors --> - <color name="board_default_color">#31CC7C</color> + <color name="board_default_color">#b6469d</color> <color name="board_default_custom_color">#616161</color> <string-array name="board_default_colors"> - <item>#31CC7C</item> - <item>#317CCC</item> - <item>#FF7A66</item> - <item>#F1DB50</item> - <item>#7C31CC</item> - <item>#CC317C</item> - <item>#3A3B3D</item> - <!-- <item>#CACBCD</item> --> + <item>#b6469d</item> + <item>#bf678b</item> + <item>#c98879</item> + <item>#ddcb55</item> + <item>#a5b872</item> + <item>#6ea68f</item> + <item>#3794ac</item> + <item>#0082c9</item> + <item>#2d73be</item> + <item>#5b64b3</item> + <item>#8855a8</item> </string-array> <color name="widget_background">#ccf5f5f5</color> diff --git a/app/src/main/res/values/setup.xml b/app/src/main/res/values/setup.xml index fb917ffb7..247bb0697 100644 --- a/app/src/main/res/values/setup.xml +++ b/app/src/main/res/values/setup.xml @@ -4,7 +4,6 @@ <string name="shared_preference_last_background_sync" translatable="false">it.niedermann.nextcloud.deck.last_background_sync</string> <string name="shared_preference_theme_main" translatable="false">it.niedermann.nextcloud.deck.theme_main</string> - <string name="shared_preference_theme_text" translatable="false">it.niedermann.nextcloud.deck.theme_text</string> <string name="pref_key_wifi_only" translatable="false">wifiOnly</string> <string name="pref_key_dark_theme" translatable="false">darkTheme</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f5a3c4f1..db0dc6400 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,6 +114,7 @@ <string name="url_about_icon_author" translatable="false">https://github.com/nextcloud/deck/commit/8c04ea8dc99e9b392f4039e8e5e6964d5a6d3453#diff-f3716cc279904617b1a21078526b6bf1R1</string> <string name="url_source" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck</string> <string name="url_issues" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck/issues/new/choose</string> + <string name="url_report_bug" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck/issues/new?labels=bug&template=bug_report.md</string> <string name="url_license" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck/blob/master/LICENSE</string> <string name="url_translations" translatable="false">https://www.transifex.com/nextcloud/nextcloud/</string> <string name="url_about_icons_disclaimer_mdi" translatable="false">https://materialdesignicons.com/</string> @@ -241,6 +242,7 @@ <string name="error_dialog_title">Oh no - What now? 🙁</string> <string name="error_dialog_tip_token_mismatch_retry">Please try to force close the app and restart it again. There might have been an incorrect connection to the Nextcloud app.</string> <string name="error_dialog_tip_token_mismatch_clear_storage">If the issue persists, try to clear the storage of both apps: Nextcloud and Nextcloud Deck to solve this issue.</string> + <string name="error_dialog_tip_database_upgrade_failed">The ugprade of the database failed. Please report the issue and clear the storage to use the app normally.</string> <string name="error_dialog_tip_clear_storage">You can clear the storage by opening the app info and selecting Storage → Clear storage.</string> <string name="error_dialog_tip_files_outdated">Your Nextcloud app seems to be outdated. Please visit the Play Store or F-Droid to get the latest version.</string> <string name="error_dialog_tip_files_force_stop">Something seems to be wrong with your Nextcloud app. Please try to force stop both, the Nextcloud app and the Nextcloud Deck app.</string> @@ -256,10 +258,12 @@ <string name="error_dialog_version_not_parsable">We could not determine the version of the server side Deck app. Please make sure it is installed and enabled.</string> <string name="error_dialog_capabilities_not_parsable">We could not fetch the capabilities of your server. Please make sure your server is running well and other client apps are able to access Nextcloud.</string> <string name="error_dialog_attachment_upload_failed">An attachment could not be uploaded. Please try to share it on another way and let us know about this bug.</string> + <string name="error_dialog_tip_disable_battery_optimizations">Please disable all battery optimizations for Nextcloud and the Deck app.</string> <string name="error_action_open_deck_info">Open App info</string> <string name="error_action_open_network">Network settings</string> <string name="error_action_server_logs">Server logs</string> <string name="error_action_install">Install</string> + <string name="error_action_report_issue">Report</string> <string name="info_box_maintenance_mode">Server in maintenance mode</string> <string name="info_box_version_not_supported">Server version %1$s not supported, please update to %2$s</string> <string name="share_link">Share link</string> @@ -276,6 +280,7 @@ <item quantity="other">%1$d errors while uploading</item> </plurals> <string name="simple_report">Report</string> + <string name="error_action_open_battery_settings">Battery settings</string> <string name="simple_contact">Contact</string> <string name="simple_file">File</string> <string name="simple_camera">Camera</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d6121c306..f0bff78fe 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,37 +1,23 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> +<resources> - <!-- Default is a light theme with the dark blue brand --> - <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge"> + <style name="BaseTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge"> <item name="colorPrimary">@color/primary</item> <item name="colorPrimaryDark">@color/primary</item> - <item name="colorAccent">@color/primary</item> - <item name="toolbarEditTextColor">@android:color/white</item> - <item name="toolbarEditTextHighlightColor">@color/toolbarEditTextHighlightColor</item> - <item name="android:windowContentOverlay">@null</item> - <item name="android:elevation" tools:targetApi="lollipop">@null</item> - </style> - - <!-- This is a light theme with a bright brand color like yellow --> - <style name="AppThemeLightBrand" parent="AppTheme"> - <item name="toolbarEditTextColor">@android:color/black</item> + <item name="colorAccent">@color/accent</item> + <item name="toolbarStyle">@style/toolbarStyle</item> + <item name="android:windowBackground">?attr/colorPrimary</item> </style> - <!-- This styles an EditText which is on a primary background like in the toolbar --> - <style name="EditTextOnPrimaryBackground" parent="ThemeOverlay.MaterialComponents.Dark"> - <item name="android:textColor">?toolbarEditTextColor</item> - <item name="android:textColorHighlight">?toolbarEditTextHighlightColor</item> - <item name="colorControlNormal">?toolbarEditTextColor</item> - <item name="colorControlActivated">?toolbarEditTextColor</item> - <item name="colorControlHighlight">?toolbarEditTextColor</item> + <style name="toolbarStyle" parent="@style/Widget.AppCompat.Toolbar"> + <item name="android:background">?attr/colorPrimary</item> </style> - <style name="DarkTextView" parent="Widget.AppCompat.TextView"> - <item name="android:textColor">@color/dark_fg_primary</item> - </style> + <!-- Default is a light theme with the dark blue brand --> + <style name="AppTheme" parent="BaseTheme" /> <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> <item name="android:windowBackground">@drawable/splash_screen</item> - <item name="colorPrimaryDark">@color/primary</item> + <item name="colorPrimaryDark">@color/defaultBrand</item> </style> <style name="NavigationView"> diff --git a/fastlane/metadata/android/en-US/changelogs/1006000.txt b/fastlane/metadata/android/en-US/changelogs/1006000.txt new file mode 100644 index 000000000..706c8e007 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/1006000.txt @@ -0,0 +1,5 @@ +1.6.0 + +- ✨ Adjust design to new style of Nextcloud app (#525) +- 🎨 Offer the same colors as the server app +- 🐞 "Archive cards" of list crashes the app when there is no list (#557)
\ No newline at end of file |