package yuku.alkitab.base.ac;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.ShareCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import yuku.afw.V;
import yuku.afw.storage.Preferences;
import yuku.afw.widget.EasyAdapter;
import yuku.alkitab.base.App;
import yuku.alkitab.base.S;
import yuku.alkitab.base.U;
import yuku.alkitab.base.ac.base.BaseActivity;
import yuku.alkitab.base.config.VersionConfig;
import yuku.alkitab.base.model.MVersion;
import yuku.alkitab.base.model.MVersionDb;
import yuku.alkitab.base.model.MVersionInternal;
import yuku.alkitab.base.model.MVersionPreset;
import yuku.alkitab.base.pdbconvert.ConvertOptionsDialog;
import yuku.alkitab.base.pdbconvert.ConvertPdbToYes2;
import yuku.alkitab.base.storage.YesReaderFactory;
import yuku.alkitab.base.sv.VersionConfigUpdaterService;
import yuku.alkitab.base.util.AddonManager;
import yuku.alkitab.base.util.DownloadMapper;
import yuku.alkitab.base.util.QueryTokenizer;
import yuku.alkitab.debug.BuildConfig;
import yuku.alkitab.debug.R;
import yuku.alkitab.io.BibleReader;
import yuku.filechooser.FileChooserActivity;
import yuku.filechooser.FileChooserConfig;
import yuku.filechooser.FileChooserResult;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.zip.GZIPInputStream;
public class VersionsActivity extends BaseActivity {
public static final String TAG = VersionsActivity.class.getSimpleName();
private static final int REQCODE_openFile = 1;
SectionsPagerAdapter sectionsPagerAdapter;
ViewPager viewPager;
TabLayout tablayout;
String query_text;
public static Intent createIntent() {
return new Intent(App.context, VersionsActivity.class);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.willNeedStoragePermission();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_versions);
setTitle(R.string.kelola_versi);
final Toolbar toolbar = V.get(this, R.id.toolbar);
setSupportActionBar(toolbar);
final ActionBar ab = getSupportActionBar();
assert ab != null;
ab.setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
viewPager = V.get(this, R.id.viewPager);
viewPager.setAdapter(sectionsPagerAdapter);
tablayout = V.get(this, R.id.tablayout);
tablayout.setTabMode(TabLayout.MODE_SCROLLABLE);
tablayout.setupWithViewPager(viewPager);
processIntent(getIntent(), "onCreate");
// try to auto-update version list
VersionConfigUpdaterService.checkUpdate(true);
}
private void processIntent(Intent intent, String via) {
Log.d(TAG, "Got intent via " + via);
Log.d(TAG, " action: " + intent.getAction());
Log.d(TAG, " data uri: " + intent.getData());
Log.d(TAG, " component: " + intent.getComponent());
Log.d(TAG, " flags: 0x" + Integer.toHexString(intent.getFlags()));
Log.d(TAG, " mime: " + intent.getType());
Bundle extras = intent.getExtras();
Log.d(TAG, " extras: " + (extras == null ? "null" : extras.size()));
if (extras != null) {
for (String key : extras.keySet()) {
Log.d(TAG, " " + key + " = " + extras.get(key));
}
}
checkAndProcessOpenFileIntent(intent);
}
private void checkAndProcessOpenFileIntent(Intent intent) {
if (!U.equals(intent.getAction(), Intent.ACTION_VIEW)) return;
// we are trying to open a file, so let's go to the DOWNLOADED tab, as it is more relevant.
viewPager.setCurrentItem(1);
Uri uri = intent.getData();
final boolean isLocalFile = U.equals("file", uri.getScheme());
final Boolean isYesFile; // false:pdb true:yes null:cannotdetermine
final String filelastname;
if (isLocalFile) {
String pathlc = uri.getPath().toLowerCase(Locale.US);
if (pathlc.endsWith(".yes")) {
isYesFile = true;
} else if (pathlc.endsWith(".pdb")) {
isYesFile = false;
} else {
isYesFile = null;
}
filelastname = uri.getLastPathSegment();
} else {
// try to read display name from content
final Cursor c = getContentResolver().query(uri, null, null, null, null);
if (c == null) {
new MaterialDialog.Builder(this)
.content(TextUtils.expandTemplate(getString(R.string.open_yes_error_read), uri.toString()))
.positiveText(R.string.ok)
.show();
return;
}
String[] cns = c.getColumnNames();
Log.d(TAG, Arrays.toString(cns));
c.moveToNext();
for (int i = 0, len = c.getColumnCount(); i < len; i++) {
Log.d(TAG, cns[i] + ": " + c.getString(i));
}
int col = c.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
if (col != -1) {
String name = c.getString(col);
if (name == null) {
isYesFile = null;
} else {
final String namelc = name.toLowerCase(Locale.US);
if (namelc.endsWith(".yes")) {
isYesFile = true;
} else if (namelc.endsWith(".pdb")) {
isYesFile = false;
} else {
isYesFile = null;
}
}
filelastname = name;
} else {
isYesFile = null;
filelastname = null;
}
c.close();
}
try {
if (isYesFile == null) { // can't be determined
new MaterialDialog.Builder(this)
.content(R.string.open_file_unknown_file_format)
.positiveText(R.string.ok)
.show();
return;
}
if (!isYesFile) { // pdb file
if (isLocalFile) {
handleFileOpenPdb(uri.getPath());
} else {
// copy the file to cache first
final File cacheFile = new File(getCacheDir(), "datafile");
final InputStream input = getContentResolver().openInputStream(uri);
if (input == null) {
new MaterialDialog.Builder(this)
.content(TextUtils.expandTemplate(getString(R.string.open_yes_error_read), uri.toString()))
.positiveText(R.string.ok)
.show();
return;
}
copyStreamToFile(input, cacheFile);
input.close();
handleFileOpenPdb(cacheFile.getAbsolutePath(), filelastname);
}
return;
}
if (isLocalFile) { // opening a local yes file
handleFileOpenYes(new File(uri.getPath()));
return;
}
final File existingFile = AddonManager.getReadableVersionFile(filelastname);
if (existingFile != null) {
new MaterialDialog.Builder(this)
.content(getString(R.string.open_yes_file_name_conflict, filelastname, existingFile.getAbsolutePath()))
.positiveText(R.string.ok)
.show();
return;
}
final InputStream input = getContentResolver().openInputStream(uri);
if (input == null) {
new MaterialDialog.Builder(this)
.content(TextUtils.expandTemplate(getString(R.string.open_yes_error_read), uri.toString()))
.positiveText(R.string.ok)
.show();
return;
}
final File localFile = AddonManager.getWritableVersionFile(filelastname);
copyStreamToFile(input, localFile);
input.close();
handleFileOpenYes(localFile);
} catch (Exception e) {
new MaterialDialog.Builder(this)
.content(R.string.open_file_cant_read_source)
.positiveText(R.string.ok)
.show();
}
}
private static void copyStreamToFile(InputStream input, File file) throws IOException {
OutputStream output = new BufferedOutputStream(new FileOutputStream(file));
byte[] buf = new byte[4096];
while (true) {
int read = input.read(buf, 0, buf.length);
if (read < 0) break;
output.write(buf, 0, read);
}
output.close();
}
// taken from FragmentPagerAdapter
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_versions, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
final MenuItem menuSearch = menu.findItem(R.id.menuSearch);
MenuItemCompat.setOnActionExpandListener(menuSearch, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(final MenuItem item) {
setOthersVisible(item, false);
return true;
}
@Override
public boolean onMenuItemActionCollapse(final MenuItem item) {
setOthersVisible(item, true);
return true;
}
private void setOthersVisible(final MenuItem except, final boolean visible) {
for (int i = 0, len = menu.size(); i < len; i++) {
final MenuItem other = menu.getItem(i);
if (except != other) {
other.setVisible(visible);
}
}
}
});
final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setOnQueryTextListener(searchView_change);
return true;
}
final SearchView.OnQueryTextListener searchView_change = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(final String query) {
query_text = query;
startSearch();
return false;
}
@Override
public boolean onQueryTextChange(final String newText) {
query_text = newText;
startSearch();
return false;
}
};
private void startSearch() {
// broadcast to all fragments that we have a new query_text
for (final String tag : new String[]{makeFragmentName(R.id.viewPager, 0), makeFragmentName(R.id.viewPager, 1)}) {
final Fragment f = getSupportFragmentManager().findFragmentByTag(tag);
if (f instanceof QueryTextReceiver) {
((QueryTextReceiver) f).setQueryText(query_text);
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuAddFromLocal:
clickOnOpenFile();
return true;
case R.id.menuAddFromUrl:
openUrlInputDialog(null);
return true;
}
return super.onOptionsItemSelected(item);
}
void clickOnOpenFile() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
FileChooserConfig config = new FileChooserConfig();
config.mode = FileChooserConfig.Mode.Open;
config.initialDir = Environment.getExternalStorageDirectory().getAbsolutePath();
config.title = getString(R.string.ed_choose_pdb_or_yes_file);
config.pattern = ".*\\.(?i:pdb|yes|yes\\.gz)";
startActivityForResult(FileChooserActivity.createIntent(App.context, config), REQCODE_openFile);
} else {
new MaterialDialog.Builder(this)
.content(R.string.ed_no_external_storage)
.positiveText(R.string.ok)
.show();
}
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return VersionListFragment.newInstance(position == 1, query_text);
}
@Override
public int getCount() {
return 2;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.ed_section_all);
case 1:
return getString(R.string.ed_section_downloaded);
}
return null;
}
}
private void handleFileOpenPdb(final String pdbFilename) {
handleFileOpenPdb(pdbFilename, null);
}
private void handleFileOpenPdb(final String pdbFilename, final String filelastname) {
final String yesName = yesNameForPdb(filelastname != null ? filelastname : pdbFilename);
// check if it exists previously
if (AddonManager.getReadableVersionFile(yesName) != null) {
new MaterialDialog.Builder(this)
.content(R.string.ed_this_file_is_already_on_the_list)
.positiveText(R.string.ok)
.show();
return;
}
final ConvertOptionsDialog.ConvertOptionsCallback callback = new ConvertOptionsDialog.ConvertOptionsCallback() {
private void showPdbReadErrorDialog(Throwable exception) {
final StringWriter sw = new StringWriter(400);
sw.append('(').append(exception.getClass().getName()).append("): ").append(exception.getMessage()).append('\n');
exception.printStackTrace(new PrintWriter(sw));
new MaterialDialog.Builder(VersionsActivity.this)
.title(R.string.ed_error_reading_pdb_file)
.content(exception instanceof ConvertOptionsDialog.PdbKnownErrorException ? exception.getMessage() : (getString(R.string.ed_details) + sw.toString()))
.positiveText(R.string.ok)
.show();
}
private void showResult(final File yesFile, Throwable exception, List<String> wronglyConvertedBookNames) {
if (exception != null) {
App.trackEvent("versions_convert_pdb_error");
showPdbReadErrorDialog(exception);
} else {
// success.
App.trackEvent("versions_convert_pdb_success");
handleFileOpenYes(yesFile);
if (wronglyConvertedBookNames != null && wronglyConvertedBookNames.size() > 0) {
StringBuilder msg = new StringBuilder(getString(R.string.ed_the_following_books_from_the_pdb_file_are_not_recognized) + '\n');
for (String s : wronglyConvertedBookNames) {
msg.append("- ").append(s).append('\n');
}
new MaterialDialog.Builder(VersionsActivity.this)
.content(msg)
.positiveText(R.string.ok)
.show();
}
}
}
@Override
public void onPdbReadError(Throwable e) {
showPdbReadErrorDialog(e);
}
@Override
public void onOkYes2(final ConvertPdbToYes2.ConvertParams params) {
final File yesFile = AddonManager.getWritableVersionFile(yesName);
final MaterialDialog pd = new MaterialDialog.Builder(VersionsActivity.this)
.content(R.string.ed_reading_pdb_file)
.cancelable(false)
.progress(true, 0)
.show();
new AsyncTask<String, Object, ConvertPdbToYes2.ConvertResult>() {
@Override
protected ConvertPdbToYes2.ConvertResult doInBackground(String... _unused_) {
ConvertPdbToYes2 converter = new ConvertPdbToYes2();
converter.setConvertProgressListener(new ConvertPdbToYes2.ConvertProgressListener() {
@Override
public void onProgress(int at, String message) {
Log.d(TAG, "Progress " + at + ": " + message);
publishProgress(at, message);
}
@Override
public void onFinish() {
Log.d(TAG, "Finish");
publishProgress(null, null);
}
});
return converter.convert(App.context, pdbFilename, yesFile, params);
}
@Override
protected void onProgressUpdate(Object... values) {
if (values[0] == null) {
pd.setContent(getString(R.string.ed_finished));
} else {
int at = (Integer) values[0];
String message = (String) values[1];
pd.setContent("(" + at + ") " + message + "...");
}
}
@Override
protected void onPostExecute(ConvertPdbToYes2.ConvertResult result) {
pd.dismiss();
showResult(yesFile, result.exception, result.wronglyConvertedBookNames);
}
}.execute();
}
};
App.trackEvent("versions_convert_pdb_start");
ConvertOptionsDialog dialog = new ConvertOptionsDialog(this, pdbFilename, callback);
dialog.show();
}
void handleFileOpenYes(File file) {
try {
final BibleReader reader = YesReaderFactory.createYesReader(file.getAbsolutePath());
if (reader == null) {
throw new Exception("Not a valid YES file.");
}
int maxOrdering = S.getDb().getVersionMaxOrdering();
if (maxOrdering == 0) maxOrdering = MVersionDb.DEFAULT_ORDERING_START;
final MVersionDb mvDb = new MVersionDb();
mvDb.locale = reader.getLocale();
mvDb.shortName = reader.getShortName();
mvDb.longName = reader.getLongName();
mvDb.description = reader.getDescription();
mvDb.filename = file.getAbsolutePath();
mvDb.ordering = maxOrdering + 1;
mvDb.preset_name = null;
S.getDb().insertOrUpdateVersionWithActive(mvDb, true);
MVersionDb.clearVersionImplCache();
App.getLbm().sendBroadcast(new Intent(VersionListFragment.ACTION_RELOAD));
} catch (Exception e) {
new MaterialDialog.Builder(this)
.title(R.string.ed_error_encountered)
.content(e.getClass().getSimpleName() + ": " + e.getMessage())
.positiveText(R.string.ok)
.show();
}
}
/**
* @return a filename for yes that will be converted from pdb file, such as "pdb-XXX.yes"
* XXX is the original filename without the .pdb or .PDB ending, converted to lowercase.
* All except alphanumeric and . - _ are stripped.
* Path not included.
* <p>
* Previously it was like "pdb-1234abcd-1.yes".
*/
private String yesNameForPdb(String filenamepdb) {
String base = new File(filenamepdb).getName().toLowerCase(Locale.US);
if (base.endsWith(".pdb")) {
base = base.substring(0, base.length() - 4);
}
base = base.replaceAll("[^0-9a-z_\\.-]", "");
return "pdb-" + base + ".yes";
}
void openUrlInputDialog(@Nullable final String prefill) {
new MaterialDialog.Builder(this)
.input(getText(R.string.version_download_add_from_url_prompt_yes_only), prefill, false, (dialog, input) -> {
final String url = input.toString().trim();
if (url.length() == 0) {
return;
}
final Uri uri = Uri.parse(url);
final String scheme = uri.getScheme();
if (!U.equals(scheme, "http") && !U.equals(scheme, "https")) {
new MaterialDialog.Builder(VersionsActivity.this)
.content(R.string.version_download_invalid_url)
.positiveText(R.string.ok)
.onPositive((dialog1, which) -> openUrlInputDialog(url))
.show();
return;
}
// guess destination filename
final String last = uri.getLastPathSegment();
if (TextUtils.isEmpty(last) || !last.toLowerCase(Locale.US).endsWith(".yes")) {
new MaterialDialog.Builder(VersionsActivity.this)
.content(R.string.version_download_not_yes)
.positiveText(R.string.ok)
.show();
return;
}
final String downloadKey = "version:url:" + url;
final int status = DownloadMapper.instance.getStatus(downloadKey);
if (status == DownloadManager.STATUS_PENDING || status == DownloadManager.STATUS_RUNNING) {
// it's downloading!
return;
}
final DownloadManager.Request req = new DownloadManager.Request(Uri.parse(url))
.setTitle(last)
.setVisibleInDownloadsUi(false)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
final Map<String, String> attrs = new LinkedHashMap<>();
attrs.put("download_type", "url");
attrs.put("filename_last_segment", last);
DownloadMapper.instance.enqueue(downloadKey, req, attrs);
Toast.makeText(this, R.string.mulai_mengunduh, Toast.LENGTH_SHORT).show();
})
.positiveText(R.string.ok)
.show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQCODE_openFile) {
final FileChooserResult result = FileChooserActivity.obtainResult(data);
if (result == null) {
return;
}
// we are trying to open a file, so let's go to the DOWNLOADED tab, as it is more relevant.
viewPager.setCurrentItem(1);
final String filename = result.firstFilename;
if (filename.toLowerCase(Locale.US).endsWith(".yes.gz")) {
// decompress or see if the same filename without .gz exists
final File maybeDecompressed = new File(filename.substring(0, filename.length() - 3));
if (maybeDecompressed.exists() && !maybeDecompressed.isDirectory() && maybeDecompressed.canRead()) {
handleFileOpenYes(maybeDecompressed);
} else {
final MaterialDialog pd = new MaterialDialog.Builder(this)
.content(R.string.sedang_mendekompres_harap_tunggu)
.cancelable(false)
.progress(true, 0)
.show();
new AsyncTask<Void, Void, File>() {
@Override
protected File doInBackground(Void... params) {
String tmpfile3 = filename + "-" + (int) (Math.random() * 100000) + ".tmp3";
try {
GZIPInputStream in = new GZIPInputStream(new FileInputStream(filename));
FileOutputStream out = new FileOutputStream(tmpfile3); // decompressed file
// Transfer bytes from the compressed file to the output file
byte[] buf = new byte[4096 * 4];
while (true) {
int len = in.read(buf);
if (len <= 0) break;
out.write(buf, 0, len);
}
out.close();
in.close();
boolean renameOk = new File(tmpfile3).renameTo(maybeDecompressed);
if (!renameOk) {
throw new RuntimeException("Failed to rename!");
}
} catch (Exception e) {
return null;
} finally {
Log.d(TAG, "menghapus tmpfile3: " + tmpfile3);
//noinspection ResultOfMethodCallIgnored
new File(tmpfile3).delete();
}
return maybeDecompressed;
}
@Override
protected void onPostExecute(File result) {
pd.dismiss();
App.trackEvent("versions_open_yes_gz");
handleFileOpenYes(result);
}
}.execute();
}
} else if (filename.toLowerCase(Locale.US).endsWith(".yes")) {
App.trackEvent("versions_open_yes");
handleFileOpenYes(new File(filename));
} else if (filename.toLowerCase(Locale.US).endsWith(".pdb")) {
App.trackEvent("versions_open_pdb");
handleFileOpenPdb(filename);
} else {
new MaterialDialog.Builder(this)
.content(R.string.ed_invalid_file_selected)
.positiveText(R.string.ok)
.show();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class VersionListFragment extends Fragment implements QueryTextReceiver {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_DOWNLOADED_ONLY = "downloaded_only";
private static final String ARG_INITIAL_QUERY_TEXT = "initial_query_text";
public static final String ACTION_RELOAD = VersionListFragment.class.getName() + ".action.RELOAD";
public static final String ACTION_UPDATE_REFRESHING_STATUS = VersionListFragment.class.getName() + ".action.UPDATE_REFRESHING_STATUS";
public static final String EXTRA_refreshing = "refreshing";
private static final int REQCODE_share = 2;
private LayoutInflater inflater;
SwipeRefreshLayout swiper;
DragSortListView lsVersions;
VersionAdapter adapter;
private boolean downloadedOnly;
private String query_text;
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static VersionListFragment newInstance(final boolean downloadedOnly, final String initial_query_text) {
final VersionListFragment res = new VersionListFragment();
final Bundle args = new Bundle();
args.putBoolean(ARG_DOWNLOADED_ONLY, downloadedOnly);
args.putString(ARG_INITIAL_QUERY_TEXT, initial_query_text);
res.setArguments(args);
return res;
}
public VersionListFragment() {
}
final BroadcastReceiver br = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (ACTION_RELOAD.equals(action)) {
if (adapter != null) adapter.reload();
} else if (ACTION_UPDATE_REFRESHING_STATUS.equals(action)) {
final boolean refreshing = intent.getBooleanExtra(EXTRA_refreshing, false);
if (swiper != null) {
swiper.setRefreshing(refreshing);
}
}
}
};
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.getLbm().registerReceiver(br, new IntentFilter(ACTION_RELOAD));
App.getLbm().registerReceiver(br, new IntentFilter(ACTION_UPDATE_REFRESHING_STATUS));
downloadedOnly = getArguments().getBoolean(ARG_DOWNLOADED_ONLY);
query_text = getArguments().getString(ARG_INITIAL_QUERY_TEXT);
}
@Override
public void onDestroy() {
super.onDestroy();
App.getLbm().unregisterReceiver(br);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.inflater = inflater;
final View rootView = inflater.inflate(downloadedOnly ? R.layout.fragment_versions_downloaded : R.layout.fragment_versions_all, container, false);
adapter = new VersionAdapter();
lsVersions = V.get(rootView, R.id.lsVersions);
lsVersions.setAdapter(adapter);
if (downloadedOnly) {
final VersionOrderingController c = new VersionOrderingController(lsVersions);
lsVersions.setFloatViewManager(c);
lsVersions.setOnTouchListener(c);
}
swiper = V.get(rootView, R.id.swiper);
if (swiper != null) { // Can be null, if the layout used is fragment_versions_downloaded.
final int accentColor = ResourcesCompat.getColor(getResources(), R.color.accent, null);
swiper.setColorSchemeColors(accentColor, 0xffcbcbcb);
swiper.setOnRefreshListener(swiper_refresh);
}
return rootView;
}
final SwipeRefreshLayout.OnRefreshListener swiper_refresh = () -> VersionConfigUpdaterService.checkUpdate(false);
Map<String, String> cache_displayLanguage = new HashMap<>();
String getDisplayLanguage(String locale) {
if (TextUtils.isEmpty(locale)) {
return "not specified";
}
String display = cache_displayLanguage.get(locale);
if (display != null) {
return display;
}
display = new Locale(locale).getDisplayLanguage();
if (display == null || U.equals(display, locale)) {
// try asking version config locale display
display = VersionConfig.get().locale_display.get(locale);
if (display == null) {
display = locale; // can't be null now
}
}
cache_displayLanguage.put(locale, display);
return display;
}
@Override
public void setQueryText(final String query_text) {
this.query_text = query_text;
adapter.reload(); // do not broadcast, since the query only changes this fragment
}
static class Item {
MVersion mv;
boolean firstInGroup;
public Item(final MVersion mv) {
this.mv = mv;
}
}
void itemCheckboxClick(final Item item, final View itemView) {
final MVersion mv = item.mv;
if (mv instanceof MVersionPreset) {
clickOnPresetVersion(V.get(itemView, R.id.cActive), (MVersionPreset) mv);
} else if (mv instanceof MVersionDb) {
clickOnDbVersion(V.get(itemView, R.id.cActive), (MVersionDb) mv);
}
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
}
static void addDetail(final SpannableStringBuilder sb, String key, String value) {
int sb_len = sb.length();
sb.append(key.toUpperCase(Locale.getDefault())).append(": ");
sb.setSpan(new ForegroundColorSpan(0xffaaaaaa), sb_len, sb.length(), 0);
sb.setSpan(new RelativeSizeSpan(0.7f), sb_len, sb.length(), 0);
sb.setSpan(new StyleSpan(Typeface.BOLD), sb_len, sb.length(), 0);
sb.append(value);
sb.append("\n");
}
void itemNameClick(final Item item) {
final MVersion mv = item.mv;
final SpannableStringBuilder details = new SpannableStringBuilder();
if (mv instanceof MVersionInternal) addDetail(details, getString(R.string.ed_type_key), getString(R.string.ed_type_internal));
if (mv instanceof MVersionPreset) addDetail(details, getString(R.string.ed_type_key), getString(R.string.ed_type_preset));
if (mv instanceof MVersionDb) addDetail(details, getString(R.string.ed_type_key), getString(R.string.ed_type_db));
if (mv.locale != null) addDetail(details, getString(R.string.ed_locale_locale), mv.locale);
if (mv.shortName != null) addDetail(details, getString(R.string.ed_shortName_shortName), mv.shortName);
addDetail(details, getString(R.string.ed_title_title), mv.longName);
if (mv instanceof MVersionPreset) {
final MVersionPreset preset = (MVersionPreset) mv;
addDetail(details, getString(R.string.ed_default_filename_file), preset.preset_name);
addDetail(details, getString(R.string.ed_download_url_url), preset.download_url);
}
if (mv instanceof MVersionDb) {
final MVersionDb mvDb = (MVersionDb) mv;
addDetail(details, getString(R.string.ed_stored_in_file), mvDb.filename);
}
if (mv.description != null) details.append('\n').append(mv.description).append('\n');
final MaterialDialog.Builder b = new MaterialDialog.Builder(getActivity());
int button_count = 0;
// can we update?
if (mv instanceof MVersionDb && hasUpdateAvailable((MVersionDb) mv)) {
button_count++;
//noinspection ConstantConditions
b.positiveText(R.string.ed_update_button);
b.onPositive((dialog, which) -> startDownload(VersionConfig.get().getPreset(((MVersionDb) mv).preset_name)));
details.append("\n");
final int details_len = details.length();
details.append(" ");
details.setSpan(new ImageSpan(App.context, R.drawable.ic_version_update, DynamicDrawableSpan.ALIGN_BASELINE), details_len, details_len + 1, 0);
details.append(getString(R.string.ed_details_update_available));
}
// can we share?
if (mv instanceof MVersionDb && mv.hasDataFile()) {
button_count++;
b.negativeText(R.string.version_menu_share);
b.onNegative((dialog, which) -> {
final MVersionDb mvDb = (MVersionDb) mv;
final Intent intent = ShareCompat.IntentBuilder.from(getActivity())
.setType("application/octet-stream")
.addStream(Uri.fromFile(new File(mvDb.filename)))
.getIntent();
startActivityForResult(ShareActivity.createIntent(intent, getString(R.string.version_share_title)), REQCODE_share);
});
}
// can we delete?
if (mv instanceof MVersionDb) {
button_count++;
b.neutralText(R.string.buang_dari_daftar);
b.onNeutral((dialog, which) -> {
final MVersionDb mvDb = (MVersionDb) mv;
final String filename = mvDb.filename;
if (AddonManager.isInSharedStorage(filename)) {
new MaterialDialog.Builder(getActivity())
.content(getString(R.string.juga_hapus_file_datanya_file, filename))
.positiveText(R.string.delete)
.onPositive((dialog1, which1) -> {
S.getDb().deleteVersion(mvDb);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
//noinspection ResultOfMethodCallIgnored
new File(filename).delete();
})
.negativeText(R.string.no)
.onNegative((dialog1, which1) -> {
S.getDb().deleteVersion(mvDb);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
})
.neutralText(R.string.cancel)
.show();
} else { // just delete the file!
S.getDb().deleteVersion(mvDb);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
//noinspection ResultOfMethodCallIgnored
new File(filename).delete();
}
});
}
// can we download?
if (mv instanceof MVersionPreset) {
button_count++;
b.positiveText(R.string.ed_download_button);
b.onPositive((dialog, which) -> startDownload((MVersionPreset) mv));
}
// if we have no buttons at all, add a no-op OK
if (button_count == 0) {
b.positiveText(R.string.ok);
}
b.title(R.string.ed_version_details);
b.content(details);
b.show();
}
void clickOnPresetVersion(final CheckBox cActive, final MVersionPreset mv) {
if (cActive.isChecked()) {
throw new RuntimeException("THIS SHOULD NOT HAPPEN: preset may not have the active checkbox checked.");
}
startDownload(mv);
}
void startDownload(final MVersionPreset mv) {
{
int enabled = -1;
try {
enabled = App.context.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
} catch (Exception e) {
Log.d(TAG, "getting app enabled setting", e);
}
if (enabled == -1
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
new MaterialDialog.Builder(getActivity())
.content(R.string.ed_download_manager_not_enabled_prompt)
.positiveText(R.string.ok)
.onPositive((dialog, which) -> {
try {
startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.android.providers.downloads")));
} catch (ActivityNotFoundException e) {
Log.e(TAG, "opening apps setting", e);
}
})
.negativeText(R.string.cancel)
.show();
return;
}
}
final String downloadKey = "version:preset_name:" + mv.preset_name;
final int status = DownloadMapper.instance.getStatus(downloadKey);
if (status == DownloadManager.STATUS_PENDING || status == DownloadManager.STATUS_RUNNING) {
// it's downloading!
return;
}
final DownloadManager.Request req = new DownloadManager.Request(Uri.parse(mv.download_url))
.setTitle(mv.longName)
.setVisibleInDownloadsUi(false)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
final Map<String, String> attrs = new LinkedHashMap<>();
attrs.put("download_type", "preset");
attrs.put("preset_name", mv.preset_name);
attrs.put("modifyTime", "" + mv.modifyTime);
DownloadMapper.instance.enqueue(downloadKey, req, attrs);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
}
void clickOnDbVersion(final CheckBox cActive, final MVersionDb mv) {
if (cActive.isChecked()) {
mv.setActive(false);
} else {
if (mv.hasDataFile()) {
mv.setActive(true);
} else {
new MaterialDialog.Builder(getActivity())
.content(getString(R.string.the_file_for_this_version_is_no_longer_available_file, mv.filename))
.positiveText(R.string.delete)
.onPositive((dialog, which) -> {
S.getDb().deleteVersion(mv);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
})
.negativeText(R.string.no)
.show();
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQCODE_share) {
ShareActivity.Result result = ShareActivity.obtainResult(data);
if (result != null && result.chosenIntent != null) {
startActivity(result.chosenIntent);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public class VersionAdapter extends EasyAdapter implements DragSortListView.DropListener {
final List<Item> items = new ArrayList<>();
VersionAdapter() {
reload();
}
/**
* The list of versions are loaded as follows:
* - Internal version {@link yuku.alkitab.base.model.MVersionInternal}, is always there
* - Versions stored in database {@link yuku.alkitab.base.model.MVersionDb} is all loaded
* - For each non-hidden {@link yuku.alkitab.base.model.MVersionPreset} defined in {@link yuku.alkitab.base.config.VersionConfig},
* check if the {@link yuku.alkitab.base.model.MVersionPreset#preset_name} corresponds to one of the
* database version above. If it does, do not add to the resulting list. Otherwise, add it so user can download it.
* <p>
* Note: Downloaded preset version will become database version after added.
*/
void reload() {
items.clear();
{ // internal
items.add(new Item(S.getMVersionInternal()));
}
final Set<String> presetNamesInDb = new HashSet<>();
// db
for (MVersionDb mv : S.getDb().listAllVersions()) {
items.add(new Item(mv));
if (mv.preset_name != null) {
presetNamesInDb.add(mv.preset_name);
}
}
// presets (only for "all" tab)
if (!downloadedOnly) {
final boolean showHidden = Preferences.getBoolean(getString(R.string.pref_showHiddenVersion_key), false);
for (MVersionPreset preset : VersionConfig.get().presets) {
if (!showHidden && preset.hidden) continue;
if (presetNamesInDb.contains(preset.preset_name)) continue;
items.add(new Item(preset));
}
}
if (!TextUtils.isEmpty(query_text)) { // filter items based on query_text
final Matcher[] matchers = QueryTokenizer.matcherizeTokens(QueryTokenizer.tokenize(query_text));
for (int i = items.size() - 1; i >= 0; i--) {
final Item item = items.get(i);
if (!matchMatchers(item.mv, matchers)) {
items.remove(i);
}
}
}
// Sort items. For "all" tab, sort is based on display language. For "downloaded" tab, sort is based on ordering.
if (!downloadedOnly) {
Collections.sort(items, (a, b) -> {
final String locale_a = a.mv.locale;
final String locale_b = b.mv.locale;
if (U.equals(locale_a, locale_b)) {
return a.mv.longName.compareToIgnoreCase(b.mv.longName);
}
if (locale_a == null) {
return +1;
} else if (locale_b == null) {
return -1;
}
return getDisplayLanguage(locale_a).compareToIgnoreCase(getDisplayLanguage(locale_b));
});
} else {
Collections.sort(items, (a, b) -> a.mv.ordering - b.mv.ordering);
if (BuildConfig.DEBUG) {
Log.d(TAG, "ordering type versionId");
Log.d(TAG, "======== =================== =================");
for (final Item item : items) {
Log.d(TAG, String.format("%8d %-20s %s", item.mv.ordering, item.mv.getClass().getSimpleName(), item.mv.getVersionId()));
}
}
}
// mark first item in each group
String lastLocale = "<sentinel>";
for (Item item : items) {
item.firstInGroup = !U.equals(item.mv.locale, lastLocale);
lastLocale = item.mv.locale;
}
notifyDataSetChanged();
}
private boolean matchMatchers(final MVersion mv, final Matcher[] matchers) {
// have to match all tokens
for (final Matcher m : matchers) {
if (m.reset(mv.longName).find()) {
continue;
}
if (mv.shortName != null && m.reset(mv.shortName).find()) {
continue;
}
if (mv.description != null && m.reset(mv.description).find()) {
continue;
}
if (mv.locale != null && m.reset(getDisplayLanguage(mv.locale)).find()) {
continue;
}
return false;
}
return true;
}
@Override
public int getCount() {
return items.size();
}
@Override
public Item getItem(int position) {
return items.get(position);
}
@Override
public View newView(final int position, final ViewGroup parent) {
return inflater.inflate(R.layout.item_version, parent, false);
}
@Override
public void bindView(final View view, final int position, final ViewGroup parent) {
final View panelRight = V.get(view, R.id.panelRight);
final CheckBox cActive = V.get(view, R.id.cActive);
final View progress = V.get(view, R.id.progress);
final Button bLongName = V.get(view, R.id.bLongName);
final View header = V.get(view, R.id.header);
final TextView tLanguage = V.get(view, R.id.tLanguage);
final View drag_handle = V.get(view, R.id.drag_handle);
final Item item = getItem(position);
final MVersion mv = item.mv;
bLongName.setOnClickListener(v -> itemNameClick(item));
panelRight.setOnClickListener(v -> itemCheckboxClick(item, view));
cActive.setChecked(mv.getActive());
bLongName.setText(mv.longName);
tLanguage.setText(getDisplayLanguage(mv.locale));
if (mv instanceof MVersionInternal) {
cActive.setEnabled(false);
} else if (mv instanceof MVersionPreset) {
cActive.setEnabled(true);
} else if (mv instanceof MVersionDb) {
cActive.setEnabled(true);
}
if (item.firstInGroup) {
header.setVisibility(View.VISIBLE);
} else {
header.setVisibility(View.GONE);
}
// Update icon
if (mv instanceof MVersionDb && hasUpdateAvailable((MVersionDb) mv)) {
bLongName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_version_update, 0, 0, 0);
} else {
bLongName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
// downloading or not?
final boolean downloading;
if (mv instanceof MVersionInternal) {
downloading = false;
} else if (mv instanceof MVersionPreset) {
final String downloadKey = "version:preset_name:" + ((MVersionPreset) mv).preset_name;
final int status = DownloadMapper.instance.getStatus(downloadKey);
downloading = (status == DownloadManager.STATUS_PENDING || status == DownloadManager.STATUS_RUNNING);
} else if (mv instanceof MVersionDb && ((MVersionDb) mv).preset_name != null) { // probably downloading, in case of updating
final String downloadKey = "version:preset_name:" + ((MVersionDb) mv).preset_name;
final int status = DownloadMapper.instance.getStatus(downloadKey);
downloading = (status == DownloadManager.STATUS_PENDING || status == DownloadManager.STATUS_RUNNING);
} else {
downloading = false;
}
if (downloading) {
cActive.setVisibility(View.INVISIBLE);
progress.setVisibility(View.VISIBLE);
} else {
cActive.setVisibility(View.VISIBLE);
progress.setVisibility(View.INVISIBLE);
}
if (downloadedOnly) {
drag_handle.setVisibility(View.VISIBLE);
} else {
drag_handle.setVisibility(View.GONE);
}
}
@Override
public void drop(final int from, final int to) {
if (from == to) return;
final Item fromItem = getItem(from);
final Item toItem = getItem(to);
S.getDb().reorderVersions(fromItem.mv, toItem.mv);
App.getLbm().sendBroadcast(new Intent(ACTION_RELOAD));
}
}
private boolean hasUpdateAvailable(final MVersionDb mvDb) {
if (mvDb.preset_name == null || mvDb.modifyTime == 0) {
return false;
}
final int available = VersionConfig.get().getModifyTime(mvDb.preset_name);
return !(available == 0 || available <= mvDb.modifyTime);
}
private class VersionOrderingController extends DragSortController {
final DragSortListView lv;
final int dragHandleId;
public VersionOrderingController(DragSortListView lv) {
super(lv, R.id.drag_handle, DragSortController.ON_DOWN, 0);
this.dragHandleId = R.id.drag_handle;
this.lv = lv;
setRemoveEnabled(false);
}
@Override
public int startDragPosition(MotionEvent ev) {
return super.dragHandleHitPosition(ev);
}
@Override
public View onCreateFloatView(int position) {
final View res = adapter.getView(position, null, lv);
res.setBackgroundColor(0x22ffffff);
return res;
}
@Override
public void onDestroyFloatView(View floatView) {
// Do not call super and do not remove this override.
floatView.setBackgroundColor(0);
}
}
}
}
interface QueryTextReceiver {
void setQueryText(String query_text);
}