Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ plugins {
android {
namespace 'oyvindbs.zotshelf'

testOptions {
unitTests.all {
enabled = false
}
}
compileSdk 35

defaultConfig {
Expand All @@ -25,12 +20,6 @@ android {
buildConfigField "String", "ZOTERO_OAUTH_CLIENT_SECRET", "\"${System.getenv('ZOTERO_OAUTH_CLIENT_SECRET') ?: 'YOUR_CLIENT_SECRET_HERE'}\""
}

testOptions {
unitTests.all {
enabled = false
}
}

buildTypes {
release {
minifyEnabled false
Expand All @@ -48,8 +37,8 @@ android {
}

lint {
abortOnError false
checkReleaseBuilds false
abortOnError true
checkReleaseBuilds true
}
}

Expand Down
56 changes: 34 additions & 22 deletions app/src/main/java/oyvindbs/zotshelf/CollectionFragment.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package oyvindbs.zotshelf;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
Expand Down Expand Up @@ -146,9 +147,10 @@ private void loadCovers() {
new EpubCoverRepository.CoverRepositoryCallback() {
@Override
public void onCoversLoaded(List<EpubCoverItem> cachedCovers) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
if (!cachedCovers.isEmpty()) {
updateUI(cachedCovers);

Expand Down Expand Up @@ -176,9 +178,10 @@ public void onCoversLoaded(List<EpubCoverItem> cachedCovers) {

@Override
public void onError(String message) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
Log.e("CollectionFragment", "Error loading cached covers: " + message);
if (NetworkUtils.isNetworkAvailable(requireContext())) {
loadCoversFromApi();
Expand Down Expand Up @@ -245,9 +248,10 @@ public void onSuccess(List<ZoteroItem> zoteroItems) {
public void onError(String errorMessage) {
Log.e("CollectionFragment", "=== API ERROR ===");
Log.e("CollectionFragment", "Error: " + errorMessage);
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);

Expand Down Expand Up @@ -288,8 +292,9 @@ public void onSuccess(List<ZoteroItem> zoteroItems) {
zoteroItems.size() + " items from API");
processZoteroItemsForCache(zoteroItems);

if (getActivity() == null) return;
getActivity().runOnUiThread(() -> {
Activity activity = getActivity();
if (activity == null) return;
activity.runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(requireContext(), "Library updated from Zotero",
Toast.LENGTH_SHORT).show();
Expand All @@ -299,9 +304,10 @@ public void onSuccess(List<ZoteroItem> zoteroItems) {
@Override
public void onError(String errorMessage) {
Log.e("CollectionFragment", "Background update error: " + errorMessage);
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
swipeRefreshLayout.setRefreshing(false);
});
}
Expand Down Expand Up @@ -343,9 +349,10 @@ private void loadCachedCovers() {
new EpubCoverRepository.CoverRepositoryCallback() {
@Override
public void onCoversLoaded(List<EpubCoverItem> covers) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
if (covers.isEmpty()) {
showEmptyState("No cached covers found");
} else {
Expand All @@ -361,9 +368,10 @@ public void onCoversLoaded(List<EpubCoverItem> covers) {

@Override
public void onError(String message) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
showEmptyState("Error loading cached covers: " + message);
swipeRefreshLayout.setRefreshing(false);
});
Expand All @@ -373,9 +381,10 @@ public void onError(String message) {

private void processZoteroItems(List<ZoteroItem> zoteroItems) {
if (zoteroItems.isEmpty()) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);

Expand Down Expand Up @@ -497,9 +506,10 @@ public void onError(ZoteroItem item, String errorMessage) {
}

private void updateUI(final List<EpubCoverItem> newItems) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
coverItems.clear();
coverItems.addAll(newItems);

Expand Down Expand Up @@ -577,9 +587,10 @@ public void updateDisplayMode() {
}

public void applySorting() {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

getActivity().runOnUiThread(() -> {
activity.runOnUiThread(() -> {
if (!coverItems.isEmpty()) {
// Apply current sort mode
int sortMode = userPreferences.getSortMode();
Expand All @@ -601,9 +612,10 @@ public void applySorting() {
* Show an error dialog
*/
private void showErrorDialog(String title, String message) {
if (getActivity() == null) return;
Activity activity = getActivity();
if (activity == null) return;

AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", null);
Expand Down
40 changes: 40 additions & 0 deletions app/src/main/java/oyvindbs/zotshelf/CoverGridAdapter.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package oyvindbs.zotshelf;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
Expand Down Expand Up @@ -85,21 +89,57 @@ public void onBindViewHolder(@NonNull CoverViewHolder holder, int position) {
listener.onCoverClick(item);
}
});

// Set copy button click listener
holder.copyButton.setOnClickListener(v -> {
copyLinkToClipboard(item);
});
}

@Override
public int getItemCount() {
return coverItems.size();
}

private void copyLinkToClipboard(EpubCoverItem item) {
UserPreferences userPreferences = new UserPreferences(context);
int linkType = userPreferences.getLinkType();
String link;
String linkLabel;

if (linkType == UserPreferences.LINK_TYPE_INTERNAL) {
// Internal Zotero link: zotero://select/library/items/{itemId}
link = "zotero://select/library/items/" + item.getId();
linkLabel = "Internal link";
} else {
// Web library link: https://www.zotero.org/{username}/items/{itemId}
String username = item.getZoteroUsername();
if (username == null || username.isEmpty()) {
username = userPreferences.getZoteroUsername();
}
link = "https://www.zotero.org/" + username + "/items/" + item.getId();
linkLabel = "Web link";
}

// Copy to clipboard
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(linkLabel, link);
clipboard.setPrimaryClip(clip);

// Show toast notification
Toast.makeText(context, linkLabel + " copied to clipboard", Toast.LENGTH_SHORT).show();
}

static class CoverViewHolder extends RecyclerView.ViewHolder {
ImageView coverImage;
TextView titleText;
ImageButton copyButton;

public CoverViewHolder(@NonNull View itemView) {
super(itemView);
coverImage = itemView.findViewById(R.id.imageCover);
titleText = itemView.findViewById(R.id.textTitle);
copyButton = itemView.findViewById(R.id.buttonCopyLink);
}
}
}
24 changes: 24 additions & 0 deletions app/src/main/java/oyvindbs/zotshelf/SettingsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class SettingsActivity extends AppCompatActivity {
private RadioButton radioTitleOnly;
private RadioButton radioAuthorOnly;
private RadioButton radioAuthorTitle;
private RadioGroup radioGroupLinkType;
private RadioButton radioLinkWeb;
private RadioButton radioLinkInternal;
private Button buttonSave;
private Button buttonOAuthLogin;
private UserPreferences userPreferences;
Expand Down Expand Up @@ -58,6 +61,9 @@ protected void onCreate(Bundle savedInstanceState) {
radioTitleOnly = findViewById(R.id.radioTitleOnly);
radioAuthorOnly = findViewById(R.id.radioAuthorOnly);
radioAuthorTitle = findViewById(R.id.radioAuthorTitle);
radioGroupLinkType = findViewById(R.id.radioGroupLinkType);
radioLinkWeb = findViewById(R.id.radioLinkWeb);
radioLinkInternal = findViewById(R.id.radioLinkInternal);
buttonSave = findViewById(R.id.buttonSave);
buttonOAuthLogin = findViewById(R.id.buttonOAuthLogin);

Expand Down Expand Up @@ -121,6 +127,14 @@ private void loadPreferences() {
radioTitleOnly.setChecked(true);
break;
}

// Set the link type radio button
int linkType = userPreferences.getLinkType();
if (linkType == UserPreferences.LINK_TYPE_INTERNAL) {
radioLinkInternal.setChecked(true);
} else {
radioLinkWeb.setChecked(true);
}
}

private void savePreferences() {
Expand Down Expand Up @@ -166,6 +180,16 @@ private void savePreferences() {
}
userPreferences.setDisplayMode(displayMode);

// Save link type
int linkType;
int selectedLinkTypeId = radioGroupLinkType.getCheckedRadioButtonId();
if (selectedLinkTypeId == R.id.radioLinkInternal) {
linkType = UserPreferences.LINK_TYPE_INTERNAL;
} else {
linkType = UserPreferences.LINK_TYPE_WEB;
}
userPreferences.setLinkType(linkType);

Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show();
finish();
}
Expand Down
17 changes: 15 additions & 2 deletions app/src/main/java/oyvindbs/zotshelf/UserPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class UserPreferences {
private static final String KEY_SHOW_PDFS = "show_pdfs";
private static final String KEY_BOOKS_ONLY = "books_only";
private static final String KEY_SORT_MODE = "sort_mode";

private static final String KEY_LINK_TYPE = "link_type";

// Display mode constants
public static final int DISPLAY_TITLE_ONLY = 0;
public static final int DISPLAY_AUTHOR_ONLY = 1;
Expand All @@ -25,7 +26,11 @@ public class UserPreferences {
// Sort mode constants
public static final int SORT_BY_TITLE = 0;
public static final int SORT_BY_AUTHOR = 1;


// Link type constants
public static final int LINK_TYPE_WEB = 0;
public static final int LINK_TYPE_INTERNAL = 1;

private final SharedPreferences preferences;

public UserPreferences(Context context) {
Expand Down Expand Up @@ -130,6 +135,14 @@ public boolean hasAnyFileTypeEnabled() {
return getShowEpubs() || getShowPdfs();
}

public int getLinkType() {
return preferences.getInt(KEY_LINK_TYPE, LINK_TYPE_WEB); // Default to web link
}

public void setLinkType(int linkType) {
preferences.edit().putInt(KEY_LINK_TYPE, linkType).apply();
}

public void clearAll() {
preferences.edit().clear().apply();
}
Expand Down
Loading
Loading