Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
Tobias Kaminsky 2023-11-21 02:31:13 +01:00
commit 923a24498a
26 changed files with 585 additions and 552 deletions

View file

@ -20,14 +20,15 @@
*/
package com.owncloud.android.ui.activity
import android.os.Bundle
import android.view.View
/**
* Activity providing information about ways to participate in the app's development.
*/
class HuaweiCommunityActivity : CommunityActivity() {
override fun setupContent() {
super.setupContent()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.communityReleaseCandidatePlaystore.visibility = View.GONE
}
}

View file

@ -0,0 +1,32 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.utils.extensions
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.core.text.HtmlCompat
@Suppress("NewLineAtEndOfFile")
fun TextView.setHtmlContent(value: String) {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(value, HtmlCompat.FROM_HTML_MODE_LEGACY)
}

View file

@ -1,128 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Andy Scherzinger
* @author Tobias Kaminsky
* Copyright (C) 2016 Andy Scherzinger
* Copyright (C) 2016 Nextcloud
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.MenuItem;
import android.widget.TextView;
import com.google.android.material.button.MaterialButton;
import com.owncloud.android.R;
import com.owncloud.android.databinding.CommunityLayoutBinding;
import com.owncloud.android.utils.DisplayUtils;
/**
* Activity providing information about ways to participate in the app's development.
*/
public class CommunityActivity extends DrawerActivity {
protected CommunityLayoutBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = CommunityLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// setup toolbar
setupToolbar();
updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_community));
// setup drawer
setupDrawer(R.id.nav_community);
setupContent();
}
protected void setupContent() {
binding.communityReleaseCandidateText.setMovementMethod(LinkMovementMethod.getInstance());
TextView contributeForumView = binding.communityContributeForumText;
contributeForumView.setMovementMethod(LinkMovementMethod.getInstance());
contributeForumView.setText(Html.fromHtml(getString(R.string.community_contribute_forum_text) + " " +
getString(R.string.community_contribute_forum_text_link,
viewThemeUtils
.files
.primaryColorToHexString(this),
getString(R.string.help_link),
getString(R.string.community_contribute_forum_forum))));
TextView contributeTranslationView = binding.communityContributeTranslateText;
contributeTranslationView.setMovementMethod(LinkMovementMethod.getInstance());
contributeTranslationView.setText(Html.fromHtml(
getString(R.string.community_contribute_translate_link,
viewThemeUtils.files.primaryColorToHexString(this),
getString(R.string.translation_link),
getString(R.string.community_contribute_translate_translate)) + " " +
getString(R.string.community_contribute_translate_text)));
TextView contributeGithubView = binding.communityContributeGithubText;
contributeGithubView.setMovementMethod(LinkMovementMethod.getInstance());
contributeGithubView.setText(Html.fromHtml(
getString(R.string.community_contribute_github_text,
getString(R.string.community_contribute_github_text_link,
viewThemeUtils.files.primaryColorToHexString(this),
getString(R.string.contributing_link)))));
MaterialButton reportButton = binding.communityTestingReport;
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(reportButton);
reportButton.setOnClickListener(v -> DisplayUtils.startLinkIntent(this, R.string.report_issue_empty_link));
binding.communityBetaFdroid.setOnClickListener(
l -> DisplayUtils.startLinkIntent(this, R.string.fdroid_beta_link));
binding.communityReleaseCandidateFdroid.setOnClickListener(
l -> DisplayUtils.startLinkIntent(this, R.string.fdroid_link));
binding.communityReleaseCandidatePlaystore.setOnClickListener(
l -> DisplayUtils.startLinkIntent(this, R.string.play_store_register_beta));
binding.communityBetaApk.setOnClickListener(
l -> DisplayUtils.startLinkIntent(this, R.string.beta_apk_link));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean retval = true;
if (item.getItemId() == android.R.id.home) {
if (isDrawerOpen()) {
closeDrawer();
} else {
openDrawer();
}
} else {
retval = super.onOptionsItemSelected(item);
}
return retval;
}
@Override
protected void onResume() {
super.onResume();
setDrawerMenuItemChecked(R.id.nav_community);
}
}

View file

@ -0,0 +1,146 @@
/*
* Nextcloud Android client application
*
* @author Andy Scherzinger
* @author Tobias Kaminsky
* Copyright (C) 2016 Andy Scherzinger
* Copyright (C) 2016 Nextcloud
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.MenuItem
import com.nextcloud.utils.extensions.setHtmlContent
import com.owncloud.android.R
import com.owncloud.android.databinding.CommunityLayoutBinding
import com.owncloud.android.utils.DisplayUtils
/**
* Activity providing information about ways to participate in the app's development.
*/
open class CommunityActivity : DrawerActivity() {
lateinit var binding: CommunityLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CommunityLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
setupToolbar()
updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_community))
setupDrawer(R.id.nav_community)
binding.communityReleaseCandidateText.movementMethod = LinkMovementMethod.getInstance()
setupContributeForumView()
setupContributeTranslationView()
setupContributeGithubView()
setupReportButton()
setOnClickListeners()
}
private fun setupContributeForumView() {
val htmlContent = getString(R.string.community_contribute_forum_text) + " " +
getString(
R.string.community_contribute_forum_text_link,
viewThemeUtils.files
.primaryColorToHexString(this),
getString(R.string.help_link),
getString(R.string.community_contribute_forum_forum)
)
binding.communityContributeForumText.setHtmlContent(htmlContent)
}
private fun setupContributeTranslationView() {
val htmlContent = getString(
R.string.community_contribute_translate_link,
viewThemeUtils.files.primaryColorToHexString(this),
getString(R.string.translation_link),
getString(R.string.community_contribute_translate_translate)
) + " " +
getString(R.string.community_contribute_translate_text)
binding.communityContributeTranslateText.setHtmlContent(htmlContent)
}
private fun setupContributeGithubView() {
val htmlContent = getString(
R.string.community_contribute_github_text,
getString(
R.string.community_contribute_github_text_link,
viewThemeUtils.files.primaryColorToHexString(this),
getString(R.string.contributing_link)
)
)
binding.communityContributeGithubText.setHtmlContent(htmlContent)
}
private fun setupReportButton() {
val reportButton = binding.communityTestingReport
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(reportButton)
reportButton.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.report_issue_empty_link
)
}
}
private fun setOnClickListeners() {
binding.communityBetaFdroid.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.fdroid_beta_link
)
}
binding.communityReleaseCandidateFdroid.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.fdroid_link
)
}
binding.communityReleaseCandidatePlaystore.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.play_store_register_beta
)
}
binding.communityBetaApk.setOnClickListener {
DisplayUtils.startLinkIntent(
this,
R.string.beta_apk_link
)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var retval = true
if (item.itemId == android.R.id.home) {
if (isDrawerOpen) {
closeDrawer()
} else {
openDrawer()
}
} else {
retval = super.onOptionsItemSelected(item)
}
return retval
}
override fun onResume() {
super.onResume()
setDrawerMenuItemChecked(R.id.nav_community)
}
}

View file

@ -18,40 +18,29 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity
package com.owncloud.android.ui.activity;
import android.os.Bundle;
import android.view.View;
import com.owncloud.android.R;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle
import com.owncloud.android.R
import com.owncloud.android.ui.fragment.OCFileListFragment
/**
* File picker of remote files
*/
public class FilePickerActivity extends FolderPickerActivity {
class FilePickerActivity : FolderPickerActivity() {
@Override
public void onClick(View v) {
super.onClick(v);
}
@Override
protected void createFragments() {
OCFileListFragment listOfFiles = new OCFileListFragment();
Bundle args = new Bundle();
args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true);
args.putBoolean(OCFileListFragment.ARG_HIDE_FAB, true);
args.putBoolean(OCFileListFragment.ARG_HIDE_ITEM_OPTIONS, true);
args.putBoolean(OCFileListFragment.ARG_SEARCH_ONLY_FOLDER, false);
args.putBoolean(OCFileListFragment.ARG_FILE_SELECTABLE, true);
args.putString(OCFileListFragment.ARG_MIMETYPE, getIntent().getStringExtra(OCFileListFragment.ARG_MIMETYPE));
listOfFiles.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS);
transaction.commit();
override fun createFragments() {
val listOfFiles = OCFileListFragment()
val args = Bundle()
args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true)
args.putBoolean(OCFileListFragment.ARG_HIDE_FAB, true)
args.putBoolean(OCFileListFragment.ARG_HIDE_ITEM_OPTIONS, true)
args.putBoolean(OCFileListFragment.ARG_SEARCH_ONLY_FOLDER, false)
args.putBoolean(OCFileListFragment.ARG_FILE_SELECTABLE, true)
args.putString(OCFileListFragment.ARG_MIMETYPE, intent.getStringExtra(OCFileListFragment.ARG_MIMETYPE))
listOfFiles.arguments = args
val transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS)
transaction.commit()
}
}

View file

@ -616,6 +616,7 @@ open class FolderPickerActivity :
const val MOVE_OR_COPY = "MOVE_OR_COPY"
const val CHOOSE_LOCATION = "CHOOSE_LOCATION"
private val TAG = FolderPickerActivity::class.java.simpleName
protected const val TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS"
const val TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS"
}
}

View file

@ -1,373 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Andy Scherzinger
* @author Mario Danic
* @author Chris Narkiewicz
* Copyright (C) 2017 Andy Scherzinger
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.jobs.NotificationWork;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.java.util.Optional;
import com.owncloud.android.R;
import com.owncloud.android.databinding.NotificationsLayoutBinding;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.notifications.GetNotificationsRemoteOperation;
import com.owncloud.android.lib.resources.notifications.models.Notification;
import com.owncloud.android.ui.adapter.NotificationListAdapter;
import com.owncloud.android.ui.asynctasks.DeleteAllNotificationsTask;
import com.owncloud.android.ui.notifications.NotificationsContract;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.PushUtils;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager;
/**
* Activity displaying all server side stored notification items.
*/
public class NotificationsActivity extends DrawerActivity implements NotificationsContract.View {
private static final String TAG = NotificationsActivity.class.getSimpleName();
private NotificationsLayoutBinding binding;
private NotificationListAdapter adapter;
private Snackbar snackbar;
private OwnCloudClient client;
private Optional<User> optionalUser;
@Inject ClientFactory clientFactory;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log_OC.v(TAG, "onCreate() start");
super.onCreate(savedInstanceState);
binding = NotificationsLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
optionalUser = getUser();
// use account from intent (opened via android notification can have a different account than current one)
if (getIntent() != null && getIntent().getExtras() != null) {
String accountName = getIntent().getExtras().getString(NotificationWork.KEY_NOTIFICATION_ACCOUNT);
if (accountName != null && optionalUser.isPresent()) {
User user = optionalUser.get();
if (user.getAccountName().equalsIgnoreCase(accountName)) {
accountManager.setCurrentOwnCloudAccount(accountName);
setUser(getUserAccountManager().getUser());
optionalUser = getUser();
}
}
}
// setup toolbar
setupToolbar();
updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_item_notifications));
viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingList);
viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingEmpty);
// setup drawer
setupDrawer(R.id.nav_notifications);
if (!optionalUser.isPresent()) {
// show error
runOnUiThread(() -> setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.account_not_found))
);
return;
}
binding.swipeContainingList.setOnRefreshListener(() -> {
setLoadingMessage();
binding.swipeContainingList.setRefreshing(true);
fetchAndSetData();
});
binding.swipeContainingEmpty.setOnRefreshListener(() -> {
setLoadingMessageEmpty();
fetchAndSetData();
});
setupPushWarning();
setupContent();
}
private void setupPushWarning() {
if (!getResources().getBoolean(R.bool.show_push_warning)) {
return;
}
if (snackbar != null) {
if (!snackbar.isShown()) {
snackbar.show();
}
} else {
String pushUrl = getResources().getString(R.string.push_server_url);
if (pushUrl.isEmpty()) {
snackbar = Snackbar.make(binding.emptyList.emptyListView,
R.string.push_notifications_not_implemented,
Snackbar.LENGTH_INDEFINITE);
} else {
final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(this);
final String accountName = optionalUser.isPresent() ? optionalUser.get().getAccountName() : "";
final boolean usesOldLogin = arbitraryDataProvider.getBooleanValue(accountName,
UserAccountManager.ACCOUNT_USES_STANDARD_PASSWORD);
if (usesOldLogin) {
snackbar = Snackbar.make(binding.emptyList.emptyListView,
R.string.push_notifications_old_login,
Snackbar.LENGTH_INDEFINITE);
} else {
String pushValue = arbitraryDataProvider.getValue(accountName, PushUtils.KEY_PUSH);
if (pushValue == null || pushValue.isEmpty()) {
snackbar = Snackbar.make(binding.emptyList.emptyListView,
R.string.push_notifications_temp_error,
Snackbar.LENGTH_INDEFINITE);
}
}
}
if (snackbar != null && !snackbar.isShown()) {
snackbar.show();
}
}
}
@Override
public void openDrawer() {
super.openDrawer();
if (snackbar != null && snackbar.isShown()) {
snackbar.dismiss();
}
}
@Override
public void closeDrawer() {
super.closeDrawer();
setupPushWarning();
}
/**
* sets up the UI elements and loads all notification items.
*/
private void setupContent() {
binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_notification);
setLoadingMessageEmpty();
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
binding.list.setLayoutManager(layoutManager);
fetchAndSetData();
}
@VisibleForTesting
public void populateList(List<Notification> notifications) {
initializeAdapter();
adapter.setNotificationItems(notifications);
binding.loadingContent.setVisibility(View.GONE);
if (notifications.size() > 0) {
binding.swipeContainingEmpty.setVisibility(View.GONE);
binding.swipeContainingList.setVisibility(View.VISIBLE);
} else {
setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.notifications_no_results_message)
);
binding.swipeContainingList.setVisibility(View.GONE);
binding.swipeContainingEmpty.setVisibility(View.VISIBLE);
}
}
private void fetchAndSetData() {
Thread t = new Thread(() -> {
initializeAdapter();
GetNotificationsRemoteOperation getRemoteNotificationOperation = new GetNotificationsRemoteOperation();
final RemoteOperationResult<List<Notification>> result = getRemoteNotificationOperation.execute(client);
if (result.isSuccess() && result.getResultData() != null) {
runOnUiThread(() -> populateList(result.getResultData()));
} else {
Log_OC.d(TAG, result.getLogMessage());
// show error
runOnUiThread(() -> setEmptyContent(getString(R.string.notifications_no_results_headline), result.getLogMessage()));
}
hideRefreshLayoutLoader();
});
t.start();
}
private void initializeClient() {
if (client == null && optionalUser.isPresent()) {
try {
User user = optionalUser.get();
client = clientFactory.create(user);
} catch (ClientFactory.CreationException e) {
Log_OC.e(TAG, "Error initializing client", e);
}
}
}
private void initializeAdapter() {
initializeClient();
if (adapter == null) {
adapter = new NotificationListAdapter(client, this, viewThemeUtils);
binding.list.setAdapter(adapter);
}
}
private void hideRefreshLayoutLoader() {
runOnUiThread(() -> {
binding.swipeContainingList.setRefreshing(false);
binding.swipeContainingEmpty.setRefreshing(false);
});
}
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_notifications, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean retval = true;
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
if (isDrawerOpen()) {
closeDrawer();
} else {
openDrawer();
}
} else if (itemId == R.id.action_empty_notifications) {
new DeleteAllNotificationsTask(client, this).execute();
} else {
retval = super.onOptionsItemSelected(item);
}
return retval;
}
private void setLoadingMessage() {
binding.swipeContainingEmpty.setVisibility(View.GONE);
}
@VisibleForTesting
public void setLoadingMessageEmpty() {
binding.swipeContainingList.setVisibility(View.GONE);
binding.emptyList.emptyListView.setVisibility(View.GONE);
binding.loadingContent.setVisibility(View.VISIBLE);
}
@VisibleForTesting
public void setEmptyContent(String headline, String message) {
binding.swipeContainingList.setVisibility(View.GONE);
binding.loadingContent.setVisibility(View.GONE);
binding.swipeContainingEmpty.setVisibility(View.VISIBLE);
binding.emptyList.emptyListView.setVisibility(View.VISIBLE);
binding.emptyList.emptyListViewHeadline.setText(headline);
binding.emptyList.emptyListViewText.setText(message);
binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_notification);
binding.emptyList.emptyListViewText.setVisibility(View.VISIBLE);
binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE);
}
@Override
protected void onResume() {
super.onResume();
setDrawerMenuItemChecked(R.id.nav_notifications);
}
@Override
public void onRemovedNotification(boolean isSuccess) {
if (!isSuccess) {
DisplayUtils.showSnackMessage(this, getString(R.string.remove_notification_failed));
fetchAndSetData();
}
}
@Override
public void removeNotification(NotificationListAdapter.NotificationViewHolder holder) {
adapter.removeNotification(holder);
if (adapter.getItemCount() == 0) {
setEmptyContent(getString(R.string.notifications_no_results_headline), getString(R.string.notifications_no_results_message));
binding.swipeContainingList.setVisibility(View.GONE);
binding.loadingContent.setVisibility(View.GONE);
binding.swipeContainingEmpty.setVisibility(View.VISIBLE);
}
}
@Override
public void onRemovedAllNotifications(boolean isSuccess) {
if (isSuccess) {
adapter.removeAllNotifications();
setEmptyContent(getString(R.string.notifications_no_results_headline), getString(R.string.notifications_no_results_message));
binding.loadingContent.setVisibility(View.GONE);
binding.swipeContainingList.setVisibility(View.GONE);
binding.swipeContainingEmpty.setVisibility(View.VISIBLE);
} else {
DisplayUtils.showSnackMessage(this, getString(R.string.clear_notifications_failed));
}
}
@Override
public void onActionCallback(boolean isSuccess,
Notification notification,
NotificationListAdapter.NotificationViewHolder holder) {
if (isSuccess) {
adapter.removeNotification(holder);
} else {
adapter.setButtons(holder, notification);
DisplayUtils.showSnackMessage(this, getString(R.string.notification_action_failed));
}
}
}

View file

@ -0,0 +1,376 @@
/*
* Nextcloud Android client application
*
* @author Andy Scherzinger
* @author Mario Danic
* @author Chris Narkiewicz
* Copyright (C) 2017 Andy Scherzinger
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.jobs.NotificationWork
import com.nextcloud.client.network.ClientFactory.CreationException
import com.nextcloud.java.util.Optional
import com.owncloud.android.R
import com.owncloud.android.databinding.NotificationsLayoutBinding
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.notifications.GetNotificationsRemoteOperation
import com.owncloud.android.lib.resources.notifications.models.Notification
import com.owncloud.android.ui.adapter.NotificationListAdapter
import com.owncloud.android.ui.adapter.NotificationListAdapter.NotificationViewHolder
import com.owncloud.android.ui.asynctasks.DeleteAllNotificationsTask
import com.owncloud.android.ui.notifications.NotificationsContract
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.PushUtils
/**
* Activity displaying all server side stored notification items.
*/
class NotificationsActivity : DrawerActivity(), NotificationsContract.View {
private lateinit var binding: NotificationsLayoutBinding
private var adapter: NotificationListAdapter? = null
private var snackbar: Snackbar? = null
private var client: OwnCloudClient? = null
private var optionalUser: Optional<User>? = null
override fun onCreate(savedInstanceState: Bundle?) {
Log_OC.v(TAG, "onCreate() start")
super.onCreate(savedInstanceState)
binding = NotificationsLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
optionalUser = user
intent?.let {
it.extras?.let { bundle ->
setupUser(bundle)
}
}
setupToolbar()
updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_item_notifications))
setupDrawer(R.id.nav_notifications)
if (optionalUser?.isPresent == false) {
showError()
}
setupContainingList()
setupPushWarning()
setupContent()
}
private fun setupContainingList() {
viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingList)
viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingEmpty)
binding.swipeContainingList.setOnRefreshListener {
setLoadingMessage()
binding.swipeContainingList.isRefreshing = true
fetchAndSetData()
}
binding.swipeContainingEmpty.setOnRefreshListener {
setLoadingMessageEmpty()
fetchAndSetData()
}
}
private fun setupUser(bundle: Bundle) {
val accountName = bundle.getString(NotificationWork.KEY_NOTIFICATION_ACCOUNT)
if (accountName != null && optionalUser?.isPresent == true) {
val user = optionalUser?.get()
if (user?.accountName.equals(accountName, ignoreCase = true)) {
accountManager.setCurrentOwnCloudAccount(accountName)
setUser(userAccountManager.user)
optionalUser = getUser()
}
}
}
private fun showError() {
runOnUiThread {
setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.account_not_found)
)
}
return
}
private fun setupPushWarning() {
if (!resources.getBoolean(R.bool.show_push_warning)) {
return
}
if (snackbar != null) {
if (snackbar?.isShown == false) {
snackbar?.show()
}
} else {
val pushUrl = resources.getString(R.string.push_server_url)
if (pushUrl.isEmpty()) {
snackbar = Snackbar.make(
binding.emptyList.emptyListView,
R.string.push_notifications_not_implemented,
Snackbar.LENGTH_INDEFINITE
)
} else {
val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(this)
val accountName: String = if (optionalUser?.isPresent == true) {
optionalUser?.get()?.accountName ?: ""
} else {
""
}
val usesOldLogin = arbitraryDataProvider.getBooleanValue(
accountName,
UserAccountManager.ACCOUNT_USES_STANDARD_PASSWORD
)
if (usesOldLogin) {
snackbar = Snackbar.make(
binding.emptyList.emptyListView,
R.string.push_notifications_old_login,
Snackbar.LENGTH_INDEFINITE
)
} else {
val pushValue = arbitraryDataProvider.getValue(accountName, PushUtils.KEY_PUSH)
if (pushValue.isEmpty()) {
snackbar = Snackbar.make(
binding.emptyList.emptyListView,
R.string.push_notifications_temp_error,
Snackbar.LENGTH_INDEFINITE
)
}
}
}
if (snackbar != null && snackbar?.isShown == false) {
snackbar?.show()
}
}
}
override fun openDrawer() {
super.openDrawer()
if (snackbar != null && snackbar?.isShown == true) {
snackbar?.dismiss()
}
}
override fun closeDrawer() {
super.closeDrawer()
setupPushWarning()
}
/**
* sets up the UI elements and loads all notification items.
*/
private fun setupContent() {
binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_notification)
setLoadingMessageEmpty()
val layoutManager = LinearLayoutManager(this)
binding.list.layoutManager = layoutManager
fetchAndSetData()
}
@VisibleForTesting
fun populateList(notifications: List<Notification>?) {
initializeAdapter()
adapter?.setNotificationItems(notifications)
binding.loadingContent.visibility = View.GONE
if (notifications?.isNotEmpty() == true) {
binding.swipeContainingEmpty.visibility = View.GONE
binding.swipeContainingList.visibility = View.VISIBLE
} else {
setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.notifications_no_results_message)
)
binding.swipeContainingList.visibility = View.GONE
binding.swipeContainingEmpty.visibility = View.VISIBLE
}
}
private fun fetchAndSetData() {
val t = Thread {
initializeAdapter()
val getRemoteNotificationOperation = GetNotificationsRemoteOperation()
val result = getRemoteNotificationOperation.execute(client)
if (result.isSuccess && result.resultData != null) {
runOnUiThread { populateList(result.resultData) }
} else {
Log_OC.d(TAG, result.logMessage)
// show error
runOnUiThread {
setEmptyContent(
getString(R.string.notifications_no_results_headline),
result.logMessage
)
}
}
hideRefreshLayoutLoader()
}
t.start()
}
private fun initializeClient() {
if (client == null && optionalUser?.isPresent == true) {
try {
val user = optionalUser?.get()
client = clientFactory.create(user)
} catch (e: CreationException) {
Log_OC.e(TAG, "Error initializing client", e)
}
}
}
private fun initializeAdapter() {
initializeClient()
if (adapter == null) {
adapter = NotificationListAdapter(client, this, viewThemeUtils)
binding.list.adapter = adapter
}
}
private fun hideRefreshLayoutLoader() {
runOnUiThread {
binding.swipeContainingList.isRefreshing = false
binding.swipeContainingEmpty.isRefreshing = false
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_notifications, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var retval = true
val itemId = item.itemId
if (itemId == android.R.id.home) {
if (isDrawerOpen) {
closeDrawer()
} else {
openDrawer()
}
} else if (itemId == R.id.action_empty_notifications) {
DeleteAllNotificationsTask(client, this).execute()
} else {
retval = super.onOptionsItemSelected(item)
}
return retval
}
private fun setLoadingMessage() {
binding.swipeContainingEmpty.visibility = View.GONE
}
@VisibleForTesting
fun setLoadingMessageEmpty() {
binding.swipeContainingList.visibility = View.GONE
binding.emptyList.emptyListView.visibility = View.GONE
binding.loadingContent.visibility = View.VISIBLE
}
@VisibleForTesting
fun setEmptyContent(headline: String?, message: String?) {
binding.swipeContainingList.visibility = View.GONE
binding.loadingContent.visibility = View.GONE
binding.swipeContainingEmpty.visibility = View.VISIBLE
binding.emptyList.emptyListView.visibility = View.VISIBLE
binding.emptyList.emptyListViewHeadline.text = headline
binding.emptyList.emptyListViewText.text = message
binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_notification)
binding.emptyList.emptyListViewText.visibility = View.VISIBLE
binding.emptyList.emptyListIcon.visibility = View.VISIBLE
}
override fun onResume() {
super.onResume()
setDrawerMenuItemChecked(R.id.nav_notifications)
}
override fun onRemovedNotification(isSuccess: Boolean) {
if (!isSuccess) {
DisplayUtils.showSnackMessage(this, getString(R.string.remove_notification_failed))
fetchAndSetData()
}
}
override fun removeNotification(holder: NotificationViewHolder) {
adapter?.removeNotification(holder)
if (adapter?.itemCount == 0) {
setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.notifications_no_results_message)
)
binding.swipeContainingList.visibility = View.GONE
binding.loadingContent.visibility = View.GONE
binding.swipeContainingEmpty.visibility = View.VISIBLE
}
}
override fun onRemovedAllNotifications(isSuccess: Boolean) {
if (isSuccess) {
adapter?.removeAllNotifications()
setEmptyContent(
getString(R.string.notifications_no_results_headline),
getString(R.string.notifications_no_results_message)
)
binding.loadingContent.visibility = View.GONE
binding.swipeContainingList.visibility = View.GONE
binding.swipeContainingEmpty.visibility = View.VISIBLE
} else {
DisplayUtils.showSnackMessage(this, getString(R.string.clear_notifications_failed))
}
}
override fun onActionCallback(
isSuccess: Boolean,
notification: Notification,
holder: NotificationViewHolder
) {
if (isSuccess) {
adapter?.removeNotification(holder)
} else {
adapter?.setButtons(holder, notification)
DisplayUtils.showSnackMessage(this, getString(R.string.notification_action_failed))
}
}
companion object {
private val TAG = NotificationsActivity::class.java.simpleName
}
}

View file

@ -325,7 +325,6 @@
<string name="file_delete">حذف </string>
<string name="file_detail_activity_error">خطأ في استرداد النشاطات للملف</string>
<string name="file_details_no_content">حدث خطأ في تحميل التفاصيل</string>
<string name="file_downloader_notification_title_prefix">تنزيل \u0020</string>
<string name="file_icon">الملف</string>
<string name="file_keep">حفظ</string>
<string name="file_list_empty">قم برفع بعض المحتوى أو زامن مع أجهزتك</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Smazat</string>
<string name="file_detail_activity_error">Při načítání aktivit u souboru došlo k chybě</string>
<string name="file_details_no_content">Nepodařilo se načíst podrobnosti.</string>
<string name="file_downloader_notification_title_prefix">Stahování \u0020</string>
<string name="file_icon">Soubor</string>
<string name="file_keep">Ponechat</string>
<string name="file_list_empty">Nahrajte nějaký obsah, nebo synchronizujte s vašimi zařízeními.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Slet</string>
<string name="file_detail_activity_error">Fejl ved indlæsning af aktiviteter for fil</string>
<string name="file_details_no_content">Fejl ved indlæsning af detaljer</string>
<string name="file_downloader_notification_title_prefix">Downloader \u0020</string>
<string name="file_icon">Fil</string>
<string name="file_keep">Behold</string>
<string name="file_list_empty">Upload indhold eller synkronisér med dine enheder.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Löschen</string>
<string name="file_detail_activity_error">Fehler beim Abrufen der Aktivitäten für die Datei</string>
<string name="file_details_no_content">Fehler beim Laden der Details</string>
<string name="file_downloader_notification_title_prefix">Herunterladen \u0020</string>
<string name="file_icon">Datei</string>
<string name="file_keep">Behalten</string>
<string name="file_list_empty">Laden Sie Inhalt hoch oder synchronisieren Sie mit Ihren Geräten.</string>

View file

@ -327,7 +327,6 @@ Attention, la suppression est irréversible.</string>
<string name="file_delete">Supprimer</string>
<string name="file_detail_activity_error">Erreur lors de la récupération de lactivité du fichier</string>
<string name="file_details_no_content">Impossible de charger les détails</string>
<string name="file_downloader_notification_title_prefix">Téléchargement de \u0020</string>
<string name="file_icon">Fichier</string>
<string name="file_keep">Conserver</string>
<string name="file_list_empty">Déposez du contenu ou synchronisez vos appareils.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Eliminar</string>
<string name="file_detail_activity_error">Produciuse un erro ao recuperar actividades para o ficheiro</string>
<string name="file_details_no_content">Produciuse un fallo ao cargar os detalles</string>
<string name="file_downloader_notification_title_prefix">Descargando \u0020</string>
<string name="file_icon">Ficheiro</string>
<string name="file_keep">Conservar</string>
<string name="file_list_empty">Envíe algún contido ou sincronice cos seus dispositivos.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Törlés</string>
<string name="file_detail_activity_error">Hiba a fájl tevékenységeinek lekérésekor</string>
<string name="file_details_no_content">A részletek betöltése sikertelen</string>
<string name="file_downloader_notification_title_prefix">Letöltés \u0020</string>
<string name="file_icon">Fájl</string>
<string name="file_keep">Megtartás</string>
<string name="file_list_empty">Töltsön fel új tartalmat vagy szinkronizáljon az eszközeivel</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Usuń</string>
<string name="file_detail_activity_error">Błąd podczas pobierania aktywności dla pliku</string>
<string name="file_details_no_content">Nie udało się załadować szczegółów</string>
<string name="file_downloader_notification_title_prefix">Pobieranie \u0020</string>
<string name="file_icon">Plik</string>
<string name="file_keep">Zachowaj</string>
<string name="file_list_empty">Wyślij lub zsynchronizuj pliki z urządzeniami.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Удалить</string>
<string name="file_detail_activity_error">Ошибка получения истории событий, связанных с файлом</string>
<string name="file_details_no_content">Не удалось получить подробные сведения</string>
<string name="file_downloader_notification_title_prefix">Скачивание \u0020</string>
<string name="file_icon">Файл</string>
<string name="file_keep">Сохранить</string>
<string name="file_list_empty">Добавьте что-нибудь или синхронизируйте со своими устройствами!</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Обриши</string>
<string name="file_detail_activity_error">Грешка при добављању активности за фајл</string>
<string name="file_details_no_content">Грешка при учитавању детаља</string>
<string name="file_downloader_notification_title_prefix">Преузима се \u0020</string>
<string name="file_icon">Фајл</string>
<string name="file_keep">Задржи</string>
<string name="file_list_empty">Отпремите неки садржај или синхронизујте са вашим уређајима.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Ta bort</string>
<string name="file_detail_activity_error">Fel vid hämtning av aktiviteter för fil</string>
<string name="file_details_no_content">Kunde inte läsa in detaljer</string>
<string name="file_downloader_notification_title_prefix">Laddar ner \u0020</string>
<string name="file_icon">Fil</string>
<string name="file_keep">Behåll</string>
<string name="file_list_empty">Ladda upp något eller synkronisera med dina enheter</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">Sil</string>
<string name="file_detail_activity_error">Dosya işlemleri alınırken sorun çıktı</string>
<string name="file_details_no_content">Ayrıntılar yüklenemedi</string>
<string name="file_downloader_notification_title_prefix">İndiriliyor \u0020</string>
<string name="file_icon">Dosya</string>
<string name="file_keep">Tut</string>
<string name="file_list_empty">Bazı içerikler yükleyin ya da aygıtlarınızla eşitleyin.</string>

View file

@ -323,7 +323,6 @@
<string name="file_delete">Вилучити</string>
<string name="file_detail_activity_error">Помилка з отриманням дії для файлу</string>
<string name="file_details_no_content">Не вдалося завантажити подробиці</string>
<string name="file_downloader_notification_title_prefix">Звантаження \u0020</string>
<string name="file_icon">Файл</string>
<string name="file_keep">Зберегти</string>
<string name="file_list_empty">Додати дані або синхронізувати з вашими пристроями.</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">删除</string>
<string name="file_detail_activity_error">获取文件动态时出错</string>
<string name="file_details_no_content">加载详情失败</string>
<string name="file_downloader_notification_title_prefix">下载中 \u0020</string>
<string name="file_icon">文件</string>
<string name="file_keep">保留</string>
<string name="file_list_empty">上传一些内容或与您的设备同步。</string>

View file

@ -325,7 +325,6 @@
<string name="file_delete">刪除</string>
<string name="file_detail_activity_error">取得檔案活動時發生錯誤</string>
<string name="file_details_no_content">載入詳細資訊失敗</string>
<string name="file_downloader_notification_title_prefix">正在下載 \u0020</string>
<string name="file_icon">檔案</string>
<string name="file_keep">保留</string>
<string name="file_list_empty">上傳一些內容或與您的裝置同步。</string>

View file

@ -162,7 +162,6 @@
<string name="uploads_view_upload_status_fetching_server_version">Fetching server version…</string>
<string name="uploads_view_later_waiting_to_upload">Waiting to upload</string>
<string name="uploads_view_group_header" translatable="false">%1$s (%2$d)</string>
<string name="file_downloader_notification_title_prefix">Downloading \u0020</string>
<string name="downloader_download_in_progress_ticker">Downloading…</string>
<string name="downloader_download_in_progress_content">%1$d%% Downloading %2$s</string>
<string name="downloader_download_succeeded_ticker">Downloaded</string>

View file

@ -9,5 +9,10 @@ android.nonTransitiveRClass=false
android.nonFinalResIds=false
#android.debug.obsoleteApi=true
# Minimum max heap space to get reliable builds
org.gradle.jvmargs=-Xmx1g
# JVM arguments to optimize heap usage, enable heap dump on out-of-memory errors, and set the file encoding
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
kotlin.daemon.jvmargs=-Xmx4096m
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 75 warnings</span>
<span class="mdl-layout-title">Lint Report: 74 warnings</span>