mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Merge pull request #1198 from kiwix/feature/NoteKeeper
GSoC 2019 Deliverable: Note Keeper
This commit is contained in:
commit
0539d56849
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://kiwix.org/support-us/
|
415
app/src/main/java/org/kiwix/kiwixmobile/main/AddNoteDialog.java
Normal file
415
app/src/main/java/org/kiwix/kiwixmobile/main/AddNoteDialog.java
Normal file
@ -0,0 +1,415 @@
|
||||
package org.kiwix.kiwixmobile.main;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.kiwix.kiwixmobile.BuildConfig;
|
||||
import org.kiwix.kiwixmobile.R;
|
||||
import org.kiwix.kiwixmobile.data.ZimContentProvider;
|
||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.NOTES_DIRECTORY;
|
||||
|
||||
/**
|
||||
* Created by @author Aditya-Sood (21/05/19) as a part of GSoC 2019
|
||||
*
|
||||
* AddNoteDialog extends DialogFragment and is used to display the note corresponding to a particular
|
||||
* article (of a particular zim file/wiki/book) as a full-screen dialog fragment.
|
||||
*
|
||||
* Notes are saved as text files at location: "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
|
||||
* */
|
||||
|
||||
public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDialogFragment.UserClickListener {
|
||||
|
||||
public static String TAG = "AddNoteDialog";
|
||||
|
||||
private SharedPreferenceUtil sharedPreferenceUtil;
|
||||
|
||||
@BindView(R.id.add_note_toolbar)
|
||||
Toolbar toolbar; // Displays options for the note dialog
|
||||
@BindView(R.id.add_note_text_view)
|
||||
TextView addNoteTextView; // Displays article title
|
||||
@BindView(R.id.add_note_edit_text)
|
||||
EditText addNoteEditText; // Displays zim file title (wiki name)
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
private String zimFileTitle;
|
||||
private String articleTitle;
|
||||
private boolean noteFileExists = false;
|
||||
private boolean noteEdited = false; // Keeps track of state of the note (whether edited since last save)
|
||||
|
||||
public AddNoteDialog(SharedPreferenceUtil sharedPreferenceUtil) {
|
||||
this.sharedPreferenceUtil = sharedPreferenceUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setStyle(DialogFragment.STYLE_NORMAL, sharedPreferenceUtil.nightMode() ? R.style.AddNoteDialogStyle_Night : R.style.AddNoteDialogStyle);
|
||||
|
||||
zimFileTitle = ZimContentProvider.getZimFileTitle();
|
||||
articleTitle = ((MainActivity)getActivity()).getCurrentWebView().getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.dialog_add_note, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
toolbar.setTitle(R.string.note);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
closeKeyboard();
|
||||
exitAddNoteDialog();
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.share_note: // Opens app-chooser for sharing the note text file
|
||||
shareNote();
|
||||
break;
|
||||
|
||||
case R.id.save_note: // Saves the note as a text file
|
||||
saveNote(addNoteEditText.getText().toString());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.inflateMenu(R.menu.menu_add_note_dialog);
|
||||
// 'Share' disabled for empty notes, 'Save' disabled for unedited notes
|
||||
disableMenuItems();
|
||||
|
||||
addNoteTextView.setText(articleTitle);
|
||||
|
||||
// Show the previously saved note if it exists
|
||||
displayNote();
|
||||
|
||||
addNoteEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
noteEdited = true;
|
||||
enableSaveNoteMenuItem();
|
||||
enableShareNoteMenuItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Override onBackPressed() to respond to user pressing 'Back' button on navigation bar
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
return new Dialog(getActivity(), getTheme()) {
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
exitAddNoteDialog();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void exitAddNoteDialog() {
|
||||
if(noteEdited) {
|
||||
Fragment previousInstance = getActivity().getSupportFragmentManager().findFragmentByTag(ConfirmationAlertDialogFragment.TAG);
|
||||
|
||||
if(previousInstance == null) {
|
||||
// Custom AlertDialog for taking user confirmation before closing note dialog in case of unsaved changes
|
||||
DialogFragment newFragment = new ConfirmationAlertDialogFragment(sharedPreferenceUtil, TAG, R.string.confirmation_alert_dialog_message);
|
||||
newFragment.show(getActivity().getSupportFragmentManager(), ConfirmationAlertDialogFragment.TAG);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Closing unedited note dialog straightaway
|
||||
dismissAddNoteDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void disableMenuItems() {
|
||||
if(toolbar.getMenu() != null) {
|
||||
MenuItem saveItem = toolbar.getMenu().findItem(R.id.save_note);
|
||||
MenuItem shareItem = toolbar.getMenu().findItem(R.id.share_note);
|
||||
saveItem.setEnabled(false);
|
||||
shareItem.setEnabled(false);
|
||||
saveItem.getIcon().setAlpha(130);
|
||||
shareItem.getIcon().setAlpha(130);
|
||||
|
||||
} else {
|
||||
Log.d(TAG, "Toolbar without inflated menu");
|
||||
}
|
||||
}
|
||||
|
||||
private void enableSaveNoteMenuItem() {
|
||||
if(toolbar.getMenu() != null) {
|
||||
MenuItem saveItem = toolbar.getMenu().findItem(R.id.save_note);
|
||||
saveItem.setEnabled(true);
|
||||
saveItem.getIcon().setAlpha(255);
|
||||
|
||||
} else {
|
||||
Log.d(TAG, "Toolbar without inflated menu");
|
||||
}
|
||||
}
|
||||
|
||||
private void enableShareNoteMenuItem() {
|
||||
if(toolbar.getMenu() != null) {
|
||||
MenuItem shareItem = toolbar.getMenu().findItem(R.id.share_note);
|
||||
shareItem.setEnabled(true);
|
||||
shareItem.getIcon().setAlpha(255);
|
||||
|
||||
} else {
|
||||
Log.d(TAG, "Toolbar without inflated menu");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if(!noteFileExists) {
|
||||
// Prepare for input in case of empty/new note
|
||||
addNoteEditText.requestFocus();
|
||||
showKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
public void showKeyboard(){
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
}
|
||||
|
||||
public void closeKeyboard(){
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
|
||||
}
|
||||
|
||||
private void saveNote(String noteText) {
|
||||
|
||||
/* String content of the EditText, given by noteText, is saved into the text file given by:
|
||||
* "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
|
||||
* */
|
||||
|
||||
if(isExternalStorageWritable()) {
|
||||
|
||||
if(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "WRITE_EXTERNAL_STORAGE permission not granted");
|
||||
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
File notesFolder = new File(NOTES_DIRECTORY + zimFileTitle);
|
||||
boolean folderExists = true;
|
||||
|
||||
if(!notesFolder.exists()) {
|
||||
// Try creating folder if it doesn't exist
|
||||
folderExists = notesFolder.mkdirs();
|
||||
}
|
||||
|
||||
if(folderExists) {
|
||||
File noteFile = new File(notesFolder.getAbsolutePath(), articleTitle + ".txt");
|
||||
|
||||
// Save note text-file code:
|
||||
try {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(noteFile);
|
||||
fileOutputStream.write(noteText.getBytes());
|
||||
fileOutputStream.close();
|
||||
showToast(R.string.note_save_successful, Toast.LENGTH_SHORT);
|
||||
noteEdited = false; // As no unsaved changes remain
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
} else {
|
||||
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
|
||||
Log.d(TAG, "Required folder doesn't exist");
|
||||
}
|
||||
}
|
||||
else {
|
||||
showToast(R.string.note_save_error_storage_not_writable, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void displayNote() {
|
||||
|
||||
/* String content of the note text file given at:
|
||||
* "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
|
||||
* is displayed in the EditText field (note content area)
|
||||
* */
|
||||
|
||||
File noteFile = new File(NOTES_DIRECTORY + zimFileTitle + "/" + articleTitle + ".txt");
|
||||
|
||||
if(noteFile.exists()) {
|
||||
noteFileExists = true;
|
||||
|
||||
StringBuilder contents = new StringBuilder();
|
||||
try {
|
||||
|
||||
BufferedReader input = new BufferedReader(new java.io.FileReader(noteFile));
|
||||
try {
|
||||
String line = null;
|
||||
|
||||
while((line = input.readLine()) != null) {
|
||||
contents.append(line);
|
||||
contents.append(System.getProperty("line.separator"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, "Error reading line with BufferedReader");
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, "Error closing BufferedReader");
|
||||
}
|
||||
|
||||
addNoteEditText.setText(contents.toString()); // Display the note content
|
||||
|
||||
enableShareNoteMenuItem(); // As note content exists which can be shared
|
||||
}
|
||||
|
||||
// No action in case the note file for the currently open article doesn't exist
|
||||
}
|
||||
|
||||
private void shareNote() {
|
||||
|
||||
/* The note text file corresponding to the currently open article, given at:
|
||||
* "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
|
||||
* is shared via an app-chooser intent
|
||||
* */
|
||||
|
||||
if(noteEdited) {
|
||||
saveNote(addNoteEditText.getText().toString()); // Save edited note before sharing the text file
|
||||
}
|
||||
|
||||
File noteFile = new File(NOTES_DIRECTORY + zimFileTitle + "/" + articleTitle + ".txt");
|
||||
|
||||
Uri noteFileUri = null;
|
||||
if(noteFile.exists()) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
// From Nougat 7 (API 24) access to files is shared temporarily with other apps
|
||||
// Need to use FileProvider for the same
|
||||
noteFileUri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID+".fileprovider", noteFile);
|
||||
|
||||
} else {
|
||||
noteFileUri = Uri.fromFile(noteFile);
|
||||
}
|
||||
|
||||
} else {
|
||||
showToast(R.string.note_share_error_file_missing, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
if(noteFileUri != null) {
|
||||
Intent noteFileShareIntent = new Intent(Intent.ACTION_SEND);
|
||||
noteFileShareIntent.setType("application/octet-stream");
|
||||
noteFileShareIntent.putExtra(Intent.EXTRA_STREAM, noteFileUri);
|
||||
noteFileShareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Intent shareChooser = Intent.createChooser(noteFileShareIntent, getString(R.string.note_share_app_chooser_title));
|
||||
|
||||
if(noteFileShareIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
||||
startActivity(shareChooser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isExternalStorageWritable() {
|
||||
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
||||
}
|
||||
|
||||
private void showToast(int stringResource, int duration) {
|
||||
Toast.makeText(getActivity(), stringResource, duration).show();
|
||||
}
|
||||
|
||||
// Methods from ConfirmationAlertDialogFragment.UserClickListener interface
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
dismissAddNoteDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
private void dismissAddNoteDialog() {
|
||||
Dialog dialog = getDialog();
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
Dialog dialog = getDialog();
|
||||
if(dialog != null) {
|
||||
int width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
int height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
dialog.getWindow().setLayout(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (unbinder != null) {
|
||||
unbinder.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.kiwix.kiwixmobile.main;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.kiwix.kiwixmobile.R;
|
||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Created by @Aditya-Sood as a part of GSoC 2019
|
||||
*
|
||||
* This is a generic helper class for displaying a 2-button (positive & negative) confirmation dialog fragment on top of an existing dialog fragment
|
||||
* - Only for confirmation dialogs with a Positive & Negative button
|
||||
* - If you also need a Neutral button, add it selectively (if-else) for the required use case (Take care of the callback interface as well)
|
||||
*
|
||||
* Currently used as:
|
||||
* - Helper class to show the alert dialog in case the user tries to exit the {@link AddNoteDialog} with unsaved file changes
|
||||
*
|
||||
**/
|
||||
|
||||
public class ConfirmationAlertDialogFragment extends DialogFragment {
|
||||
|
||||
public static String TAG = "ConfirmationAlertDialog";
|
||||
|
||||
private SharedPreferenceUtil sharedPreferenceUtil;
|
||||
private int stringResourceId;
|
||||
private String parentDialogFragmentTAG;
|
||||
|
||||
public ConfirmationAlertDialogFragment(SharedPreferenceUtil sharedPreferenceUtil, String parentDialogFragmentTAG, int stringResourceId) {
|
||||
this.sharedPreferenceUtil = sharedPreferenceUtil;
|
||||
this.parentDialogFragmentTAG = parentDialogFragmentTAG;
|
||||
this.stringResourceId = stringResourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Fragment parentDialogFragment = getFragmentManager().findFragmentByTag(parentDialogFragmentTAG);
|
||||
|
||||
AlertDialog.Builder builder;
|
||||
|
||||
if (sharedPreferenceUtil != null && sharedPreferenceUtil.nightMode()) { // Night Mode support
|
||||
builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_Dialog_Night);
|
||||
} else {
|
||||
builder = new AlertDialog.Builder(getActivity());
|
||||
}
|
||||
|
||||
builder.setMessage(stringResourceId)
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
if(parentDialogFragment != null) {
|
||||
((UserClickListener) parentDialogFragment).onPositiveClick();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
if(parentDialogFragment != null) {
|
||||
((UserClickListener) parentDialogFragment).onNegativeClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
/** Callback interface for responding to user clicks to a {@link ConfirmationAlertDialogFragment} dialog */
|
||||
public interface UserClickListener {
|
||||
void onPositiveClick();
|
||||
|
||||
void onNegativeClick();
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ import android.appwidget.AppWidgetManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -36,6 +37,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.text.SpannableString;
|
||||
@ -70,6 +72,8 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@ -131,11 +135,15 @@ import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_LIBRARY;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_SEARCH;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_ZIM_FILE;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_ZIM_FILE_2;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.NOTES_DIRECTORY;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.PREF_KIWIX_MOBILE;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_FILE_SEARCH;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_FILE_SELECT;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_HISTORY_ITEM_CHOSEN;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_PREFERENCES;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_READ_STORAGE_PERMISSION;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.RESULT_HISTORY_CLEARED;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.RESULT_RESTART;
|
||||
import static org.kiwix.kiwixmobile.utils.Constants.TAG_CURRENT_ARTICLES;
|
||||
@ -151,8 +159,6 @@ import static org.kiwix.kiwixmobile.utils.UpdateUtils.reformatProviderUrl;
|
||||
public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
MainContract.View{
|
||||
|
||||
private static final int REQUEST_READ_STORAGE_PERMISSION = 2;
|
||||
private static final int REQUEST_HISTORY_ITEM_CHOSEN = 99;
|
||||
private static final String NEW_TAB = "NEW_TAB";
|
||||
private static final String HOME_URL = "file:///android_asset/home.html";
|
||||
public static boolean isFullscreenOpened;
|
||||
@ -445,11 +451,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
hideTabSwitcher();
|
||||
selectTab(position);
|
||||
|
||||
/* Bug Fix
|
||||
* Issue #592 in which the navigational history of the previously open tab (WebView) was
|
||||
* carried forward to the newly selected/opened tab; causing erroneous enabling of
|
||||
* navigational buttons.
|
||||
*/
|
||||
/* Bug Fix #592 */
|
||||
updateBottomToolbarArrowsAlpha();
|
||||
}
|
||||
|
||||
@ -856,6 +858,19 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
compatCallback.showSoftInput();
|
||||
break;
|
||||
|
||||
case R.id.menu_add_note:
|
||||
if(requestExternalStorageWritePermissionForNotes()) {
|
||||
// Check permission since notes are stored in the public-external storage
|
||||
showAddNoteDialog();
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.menu_clear_notes:
|
||||
if(requestExternalStorageWritePermissionForNotes()) { // Check permission since notes are stored in the public-external storage
|
||||
showClearAllNotesDialog();
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.menu_bookmarks_list:
|
||||
goToBookmarks();
|
||||
break;
|
||||
@ -915,6 +930,117 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/** Dialog to take user confirmation before deleting all notes */
|
||||
private void showClearAllNotesDialog() {
|
||||
|
||||
AlertDialog.Builder builder;
|
||||
if (sharedPreferenceUtil != null && sharedPreferenceUtil.nightMode()) { // Night Mode support
|
||||
builder = new AlertDialog.Builder(this, R.style.AppTheme_Dialog_Night);
|
||||
} else {
|
||||
builder = new AlertDialog.Builder(this);
|
||||
}
|
||||
|
||||
builder.setMessage(R.string.delete_notes_confirmation_msg)
|
||||
.setNegativeButton(android.R.string.cancel, null) // Do nothing for 'Cancel' button
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
clearAllNotes();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
/** Method to delete all user notes */
|
||||
private void clearAllNotes() {
|
||||
|
||||
boolean result = true; // Result of all delete() calls is &&-ed to this variable
|
||||
|
||||
if(AddNoteDialog.isExternalStorageWritable()) {
|
||||
if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d("MainActivity", "WRITE_EXTERNAL_STORAGE permission not granted");
|
||||
showToast(R.string.ext_storage_permission_not_granted, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Replace below code with Kotlin's deleteRecursively() method
|
||||
|
||||
File notesDirectory = new File(NOTES_DIRECTORY);
|
||||
File[] filesInNotesDirectory = notesDirectory.listFiles();
|
||||
|
||||
if(filesInNotesDirectory == null) { // Notes folder doesn't exist
|
||||
showToast(R.string.notes_deletion_none_found, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
for(File wikiFileDirectory : filesInNotesDirectory) {
|
||||
if(wikiFileDirectory.isDirectory()) {
|
||||
File[] filesInWikiDirectory = wikiFileDirectory.listFiles();
|
||||
|
||||
for(File noteFile : filesInWikiDirectory) {
|
||||
if(noteFile.isFile()) {
|
||||
result = result && noteFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = result && wikiFileDirectory.delete(); // Wiki specific notes directory deleted
|
||||
}
|
||||
|
||||
result = result && notesDirectory.delete(); // "{External Storage}/Kiwix/Notes" directory deleted
|
||||
}
|
||||
|
||||
if(result) {
|
||||
showToast(R.string.notes_deletion_successful, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
showToast(R.string.notes_deletion_unsuccessful, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates the full screen AddNoteDialog, which is a DialogFragment */
|
||||
private void showAddNoteDialog() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
Fragment previousInstance = getSupportFragmentManager().findFragmentByTag(AddNoteDialog.TAG);
|
||||
|
||||
// To prevent multiple instances of the DialogFragment
|
||||
if(previousInstance == null) {
|
||||
/* Since the DialogFragment is never added to the back-stack, so findFragmentByTag()
|
||||
* returning null means that the AddNoteDialog is currently not on display (as doesn't exist)
|
||||
**/
|
||||
AddNoteDialog dialogFragment = new AddNoteDialog(sharedPreferenceUtil);
|
||||
dialogFragment.show(fragmentTransaction, AddNoteDialog.TAG); // For DialogFragments, show() handles the fragment commit and display
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestExternalStorageWritePermissionForNotes() {
|
||||
if(Build.VERSION.SDK_INT >= 23) { // For Marshmallow & higher API levels
|
||||
|
||||
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if(shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
/* shouldShowRequestPermissionRationale() returns false when:
|
||||
* 1) User has previously checked on "Don't ask me again", and/or
|
||||
* 2) Permission has been disabled on device
|
||||
*/
|
||||
showToast(R.string.ext_storage_permission_rationale_add_note, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE);
|
||||
}
|
||||
|
||||
} else { // For Android versions below Marshmallow 6.0 (API 23)
|
||||
return true; // As already requested at install time
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showToast(int stringResource, int duration) {
|
||||
Toast.makeText(this, stringResource, duration).show();
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
@OnLongClick(R.id.bottom_toolbar_bookmark)
|
||||
boolean goToBookmarks() {
|
||||
@ -1084,6 +1210,20 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
startActivity(intent);
|
||||
}).show();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE: {
|
||||
|
||||
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Successfully granted permission, so opening the note keeper
|
||||
showAddNoteDialog();
|
||||
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.ext_storage_write_permission_denied_add_note), Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.kiwix.kiwixmobile.utils;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import org.kiwix.kiwixmobile.BuildConfig;
|
||||
|
||||
public final class Constants {
|
||||
@ -30,6 +32,12 @@ public final class Constants {
|
||||
|
||||
public static final int REQUEST_STORAGE_PERMISSION = 1;
|
||||
|
||||
public static final int REQUEST_READ_STORAGE_PERMISSION = 2;
|
||||
|
||||
public static final int REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE = 3;
|
||||
|
||||
public static final int REQUEST_HISTORY_ITEM_CHOSEN = 99;
|
||||
|
||||
public static final int REQUEST_FILE_SELECT = 1234;
|
||||
|
||||
public static final int REQUEST_PREFERENCES = 1235;
|
||||
@ -130,4 +138,7 @@ public final class Constants {
|
||||
public static final String OLD_PROVIDER_DOMAIN = "org.kiwix.zim.base";
|
||||
|
||||
public static final String NEW_PROVIDER_DOMAIN = BuildConfig.APPLICATION_ID + ".zim.base";
|
||||
|
||||
// Path Constants
|
||||
public static final String NOTES_DIRECTORY = Environment.getExternalStorageDirectory() + "/Kiwix/Notes/";
|
||||
}
|
||||
|
9
app/src/main/res/drawable/ic_add_note.xml
Normal file
9
app/src/main/res/drawable/ic_add_note.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3,18h12v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h18v-2L3,11v2z"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15,16h4v2h-4zM15,8h7v2h-7zM15,12h6v2h-6zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM14,5h-3l-1,-1L6,4L5,5L2,5v2h12z"/>
|
||||
</vector>
|
@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:tint="#ffffff"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
android:width="24dp">
|
||||
|
9
app/src/main/res/drawable/ic_save.xml
Normal file
9
app/src/main/res/drawable/ic_save.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</vector>
|
77
app/src/main/res/layout/dialog_add_note.xml
Normal file
77
app/src/main/res/layout/dialog_add_note.xml
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_add_note_dialog"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/add_note_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar_add_note_dialog"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_note_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/wiki_article_title"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:background="#000000"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/add_note_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
|
||||
android:minLines="6"
|
||||
android:gravity="top|left"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:scrollbars="vertical"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/note"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
15
app/src/main/res/menu/menu_add_note_dialog.xml
Normal file
15
app/src/main/res/menu/menu_add_note_dialog.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/share_note"
|
||||
android:icon="@drawable/baseline_share_24"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item android:id="@+id/save_note"
|
||||
android:icon="@drawable/ic_save"
|
||||
android:title="@string/save"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
@ -14,6 +14,18 @@
|
||||
app:actionLayout="@layout/ic_tab_switcher"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_add_note"
|
||||
android:title="@string/add_note"
|
||||
android:icon="@drawable/ic_add_note"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_clear_notes"
|
||||
android:title="@string/clear_all_notes"
|
||||
android:icon="@drawable/ic_baseline_delete_sweep_24px"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_new_tab"
|
||||
android:title="@string/new_tab"
|
||||
|
@ -264,4 +264,22 @@
|
||||
<string name="failed_unhandled_http_code">Unknown HTTP code received</string>
|
||||
<string name="failed_unknown">Unknown</string>
|
||||
<string name="failed_http_code">HTTP code %s</string>
|
||||
</resources>
|
||||
<string name="add_note">Add Note</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="note">Note</string>
|
||||
<string name="wiki_article_title">Wiki Article Title</string>
|
||||
<string name="ext_storage_permission_rationale_add_note">Storage access is required for Notes</string>
|
||||
<string name="ext_storage_write_permission_denied_add_note">Notes can\'t be used without access to storage</string>
|
||||
<string name="note_save_unsuccessful">Note save unsuccessful</string>
|
||||
<string name="note_save_successful">Note saved</string>
|
||||
<string name="note_save_error_storage_not_writable">Error saving note: Storage not writable</string>
|
||||
<string name="note_share_error_file_missing">Note file doesn\'t exist</string>
|
||||
<string name="note_share_app_chooser_title">Share note file with:</string>
|
||||
<string name="confirmation_alert_dialog_message">Discard unsaved changes?</string>
|
||||
<string name="clear_all_notes">Clear All Notes</string>
|
||||
<string name="delete_notes_confirmation_msg">Delete all notes?</string>
|
||||
<string name="ext_storage_permission_not_granted">Error: Storage permissions not granted</string>
|
||||
<string name="notes_deletion_none_found">No notes found for deletion</string>
|
||||
<string name="notes_deletion_successful">Entire notes folder deleted</string>
|
||||
<string name="notes_deletion_unsuccessful">Some files not deleted</string>
|
||||
</resources>
|
@ -129,5 +129,29 @@
|
||||
<style name="NeutralButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
<style name="AddNoteDialogStyle" parent="Theme.AppCompat.Dialog">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="android:textColor">#000000</item>
|
||||
<item name="android:editTextColor">#000000</item>
|
||||
<item name="android:textColorHint">@color/textDarkTertiary</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="AddNoteDialogStyle.Night" parent="Theme.AppCompat.Dialog">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="android:textColor">#ffffff</item>
|
||||
<item name="android:editTextColor">#ffffff</item>
|
||||
<item name="android:textColorHint">@color/grey</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowBackground">@color/cardview_dark_background</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user