mirror of https://github.com/kurisufriend/Clover
Taken from another project, might as well use it here. This layout did allow switching between storage devices, but I found out how the storage framework works now, and it's too much work to get it to work right now, so the selection of storage is hidden. Reading works fine but writing requires a lot more code. Might pick it up later if there's a good library that does it for us. All the current code uses the normal File api so that has to be rewritten afaik. Closes #202multisite
parent
796ad088bf
commit
b2f525ffd0
@ -0,0 +1,29 @@ |
|||||||
|
package org.floens.chan.core.model; |
||||||
|
|
||||||
|
import org.floens.chan.core.saver.StorageHelper; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public class FileItem { |
||||||
|
public File file; |
||||||
|
|
||||||
|
public FileItem(File file) { |
||||||
|
this.file = file; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isFile() { |
||||||
|
return file.isFile(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isFolder() { |
||||||
|
return file.isDirectory(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean canNavigate() { |
||||||
|
return StorageHelper.canNavigate(file); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean canOpen() { |
||||||
|
return StorageHelper.canOpen(file); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
package org.floens.chan.core.model; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class FileItems { |
||||||
|
public final File path; |
||||||
|
public final List<FileItem> fileItems; |
||||||
|
|
||||||
|
public final boolean canNavigateUp; |
||||||
|
|
||||||
|
public FileItems(File path, List<FileItem> fileItems, boolean canNavigateUp) { |
||||||
|
this.path = path; |
||||||
|
this.fileItems = fileItems; |
||||||
|
this.canNavigateUp = canNavigateUp; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
package org.floens.chan.core.saver; |
||||||
|
|
||||||
|
import android.os.FileObserver; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.floens.chan.core.model.FileItem; |
||||||
|
import org.floens.chan.core.model.FileItems; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class FileWatcher { |
||||||
|
private static final String TAG = "FileWatcher"; |
||||||
|
|
||||||
|
private static final Comparator<FileItem> FILE_COMPARATOR = new Comparator<FileItem>() { |
||||||
|
@Override |
||||||
|
public int compare(FileItem a, FileItem b) { |
||||||
|
return a.file.getName().compareToIgnoreCase(b.file.getName()); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
private final FileWatcherCallback callback; |
||||||
|
boolean initialized = false; |
||||||
|
private File startingPath; |
||||||
|
|
||||||
|
private File currentPath; |
||||||
|
|
||||||
|
private AFileObserver fileObserver; |
||||||
|
|
||||||
|
public FileWatcher(FileWatcherCallback callback, File startingPath) { |
||||||
|
this.callback = callback; |
||||||
|
this.startingPath = startingPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void initialize() { |
||||||
|
initialized = true; |
||||||
|
navigateTo(startingPath); |
||||||
|
} |
||||||
|
|
||||||
|
public File getCurrentPath() { |
||||||
|
return currentPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void navigateUp() { |
||||||
|
File parentFile = currentPath.getParentFile(); |
||||||
|
if (parentFile != null && StorageHelper.canNavigate(parentFile)) { |
||||||
|
navigateTo(parentFile); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void navigateTo(File to) { |
||||||
|
if (!StorageHelper.canNavigate(to)) { |
||||||
|
throw new IllegalArgumentException("Cannot navigate to " + to.getAbsolutePath()); |
||||||
|
} |
||||||
|
|
||||||
|
if (fileObserver != null) { |
||||||
|
fileObserver.stopWatching(); |
||||||
|
fileObserver = null; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: fileobserver is broken
|
||||||
|
// int mask = FileObserver.CREATE | FileObserver.DELETE;
|
||||||
|
// fileObserver = new AFileObserver(to.getAbsolutePath(), mask);
|
||||||
|
// fileObserver = new AFileObserver("/sdcard/");
|
||||||
|
// fileObserver.startWatching();
|
||||||
|
|
||||||
|
currentPath = to; |
||||||
|
|
||||||
|
File[] files = currentPath.listFiles(); |
||||||
|
|
||||||
|
List<FileItem> folderList = new ArrayList<>(); |
||||||
|
List<FileItem> fileList = new ArrayList<>(); |
||||||
|
for (File file : files) { |
||||||
|
if (StorageHelper.canNavigate(file)) { |
||||||
|
folderList.add(new FileItem(file)); |
||||||
|
} else if (file.isFile()) { |
||||||
|
fileList.add(new FileItem(file)); |
||||||
|
} |
||||||
|
} |
||||||
|
Collections.sort(folderList, FILE_COMPARATOR); |
||||||
|
Collections.sort(fileList, FILE_COMPARATOR); |
||||||
|
List<FileItem> items = new ArrayList<>(folderList.size() + fileList.size()); |
||||||
|
items.addAll(folderList); |
||||||
|
items.addAll(fileList); |
||||||
|
|
||||||
|
boolean canNavigateUp = StorageHelper.canNavigate(currentPath.getParentFile()); |
||||||
|
|
||||||
|
callback.onFiles(new FileItems(currentPath, items, canNavigateUp)); |
||||||
|
} |
||||||
|
|
||||||
|
private class AFileObserver extends FileObserver { |
||||||
|
public AFileObserver(String path) { |
||||||
|
super(path); |
||||||
|
} |
||||||
|
|
||||||
|
public AFileObserver(String path, int mask) { |
||||||
|
super(path, mask); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onEvent(int event, String path) { |
||||||
|
Log.d(TAG, "onEvent() called with: " + "event = [" + event + "], path = [" + path + "]"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public interface FileWatcherCallback { |
||||||
|
void onFiles(FileItems fileItems); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package org.floens.chan.core.saver; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public class StorageHelper { |
||||||
|
private static final String TAG = "StorageHelper"; |
||||||
|
|
||||||
|
public static boolean canNavigate(File file) { |
||||||
|
return file != null && !isDirectoryBlacklisted(file) && file.exists() |
||||||
|
&& file.isDirectory() && file.canRead(); |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean isDirectoryBlacklisted(File file) { |
||||||
|
String absolutePath = file.getAbsolutePath(); |
||||||
|
switch (absolutePath) { |
||||||
|
case "/storage": |
||||||
|
return true; |
||||||
|
case "/storage/emulated": |
||||||
|
return true; |
||||||
|
case "/storage/emulated/0/0": |
||||||
|
return true; |
||||||
|
case "/storage/emulated/legacy": |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean canOpen(File file) { |
||||||
|
return file != null && file.exists() && file.isFile() && file.canRead(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
package org.floens.chan.ui.adapter; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.drawable.Drawable; |
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat; |
||||||
|
import android.support.v7.widget.RecyclerView; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import org.floens.chan.R; |
||||||
|
import org.floens.chan.core.model.FileItem; |
||||||
|
import org.floens.chan.core.model.FileItems; |
||||||
|
|
||||||
|
import static org.floens.chan.utils.AndroidUtils.getAttrColor; |
||||||
|
|
||||||
|
public class FilesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
||||||
|
private static final int ITEM_TYPE_FOLDER = 0; |
||||||
|
private static final int ITEM_TYPE_FILE = 1; |
||||||
|
|
||||||
|
private FileItem highlightedItem; |
||||||
|
private FileItems fileItems; |
||||||
|
private Callback callback; |
||||||
|
|
||||||
|
public FilesAdapter(Callback callback) { |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFiles(FileItems fileItems) { |
||||||
|
this.fileItems = fileItems; |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setHighlightedItem(FileItem highlightedItem) { |
||||||
|
this.highlightedItem = highlightedItem; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
||||||
|
return new FileViewHolder(LayoutInflater.from(parent.getContext()) |
||||||
|
.inflate(R.layout.cell_file, parent, false)); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings({"ConstantConditions", "deprecation"}) |
||||||
|
@Override |
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |
||||||
|
int itemViewType = getItemViewType(position); |
||||||
|
switch (itemViewType) { |
||||||
|
case ITEM_TYPE_FILE: |
||||||
|
case ITEM_TYPE_FOLDER: { |
||||||
|
boolean isFile = itemViewType == ITEM_TYPE_FILE; |
||||||
|
|
||||||
|
FileItem item = getItem(position); |
||||||
|
FileViewHolder fileViewHolder = ((FileViewHolder) holder); |
||||||
|
fileViewHolder.text.setText(item.file.getName()); |
||||||
|
|
||||||
|
Context context = holder.itemView.getContext(); |
||||||
|
|
||||||
|
if (isFile) { |
||||||
|
fileViewHolder.image.setVisibility(View.GONE); |
||||||
|
} else { |
||||||
|
fileViewHolder.image.setVisibility(View.VISIBLE); |
||||||
|
Drawable drawable = DrawableCompat.wrap( |
||||||
|
context.getResources().getDrawable(R.drawable.ic_folder_black_24dp)); |
||||||
|
DrawableCompat.setTint(drawable, getAttrColor(context, R.attr.text_color_secondary)); |
||||||
|
fileViewHolder.image.setImageDrawable(drawable); |
||||||
|
} |
||||||
|
|
||||||
|
boolean highlighted = highlightedItem != null && highlightedItem.file.equals(item.file); |
||||||
|
if (highlighted) { |
||||||
|
fileViewHolder.itemView.setBackgroundColor(0x0e000000); |
||||||
|
} else { |
||||||
|
fileViewHolder.itemView.setBackgroundResource(R.drawable.item_background); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getItemCount() { |
||||||
|
return fileItems.fileItems.size(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getItemViewType(int position) { |
||||||
|
FileItem item = getItem(position); |
||||||
|
if (item.isFile()) { |
||||||
|
return ITEM_TYPE_FILE; |
||||||
|
} else if (item.isFolder()) { |
||||||
|
return ITEM_TYPE_FOLDER; |
||||||
|
} else { |
||||||
|
return ITEM_TYPE_FILE; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public FileItem getItem(int position) { |
||||||
|
return fileItems.fileItems.get(position); |
||||||
|
} |
||||||
|
|
||||||
|
private void onItemClicked(FileItem fileItem) { |
||||||
|
callback.onFileItemClicked(fileItem); |
||||||
|
} |
||||||
|
|
||||||
|
public class FileViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { |
||||||
|
private ImageView image; |
||||||
|
private TextView text; |
||||||
|
|
||||||
|
public FileViewHolder(View itemView) { |
||||||
|
super(itemView); |
||||||
|
image = (ImageView) itemView.findViewById(R.id.image); |
||||||
|
text = (TextView) itemView.findViewById(R.id.text); |
||||||
|
itemView.setOnClickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
FileItem item = getItem(getAdapterPosition()); |
||||||
|
onItemClicked(item); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public interface Callback { |
||||||
|
void onFileItemClicked(FileItem fileItem); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
package org.floens.chan.ui.controller; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.DialogInterface; |
||||||
|
import android.content.Intent; |
||||||
|
import android.net.Uri; |
||||||
|
import android.provider.Settings; |
||||||
|
import android.support.design.widget.FloatingActionButton; |
||||||
|
import android.support.v7.app.AlertDialog; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
import org.floens.chan.R; |
||||||
|
import org.floens.chan.controller.Controller; |
||||||
|
import org.floens.chan.core.model.FileItem; |
||||||
|
import org.floens.chan.core.model.FileItems; |
||||||
|
import org.floens.chan.core.saver.FileWatcher; |
||||||
|
import org.floens.chan.core.settings.ChanSettings; |
||||||
|
import org.floens.chan.ui.activity.StartActivity; |
||||||
|
import org.floens.chan.ui.adapter.FilesAdapter; |
||||||
|
import org.floens.chan.ui.helper.RuntimePermissionsHelper; |
||||||
|
import org.floens.chan.ui.layout.FilesLayout; |
||||||
|
import org.floens.chan.utils.AndroidUtils; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public class SaveLocationController extends Controller implements FileWatcher.FileWatcherCallback, FilesAdapter.Callback, FilesLayout.Callback, View.OnClickListener { |
||||||
|
private static final String TAG = "SaveLocationController"; |
||||||
|
|
||||||
|
private FilesLayout filesLayout; |
||||||
|
private FloatingActionButton setButton; |
||||||
|
|
||||||
|
private RuntimePermissionsHelper runtimePermissionsHelper; |
||||||
|
private boolean gotPermission = false; |
||||||
|
|
||||||
|
private FileWatcher fileWatcher; |
||||||
|
private FileItems fileItems; |
||||||
|
|
||||||
|
public SaveLocationController(Context context) { |
||||||
|
super(context); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCreate() { |
||||||
|
super.onCreate(); |
||||||
|
|
||||||
|
navigationItem.setTitle(R.string.save_location_screen); |
||||||
|
|
||||||
|
view = inflateRes(R.layout.controller_save_location); |
||||||
|
filesLayout = (FilesLayout) view.findViewById(R.id.files_layout); |
||||||
|
filesLayout.setCallback(this); |
||||||
|
setButton = (FloatingActionButton) view.findViewById(R.id.set_button); |
||||||
|
setButton.setOnClickListener(this); |
||||||
|
|
||||||
|
File saveLocation = new File(ChanSettings.saveLocation.get()); |
||||||
|
fileWatcher = new FileWatcher(this, saveLocation); |
||||||
|
|
||||||
|
runtimePermissionsHelper = ((StartActivity) context).getRuntimePermissionsHelper(); |
||||||
|
gotPermission = hasPermission(); |
||||||
|
if (gotPermission) { |
||||||
|
initialize(); |
||||||
|
} else { |
||||||
|
requestPermission(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View v) { |
||||||
|
if (v == setButton) { |
||||||
|
File currentPath = fileWatcher.getCurrentPath(); |
||||||
|
ChanSettings.saveLocation.set(currentPath.getAbsolutePath()); |
||||||
|
navigationController.popController(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFiles(FileItems fileItems) { |
||||||
|
this.fileItems = fileItems; |
||||||
|
filesLayout.setFiles(fileItems); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onBackClicked() { |
||||||
|
fileWatcher.navigateUp(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFileItemClicked(FileItem fileItem) { |
||||||
|
if (fileItem.canNavigate()) { |
||||||
|
fileWatcher.navigateTo(fileItem.file); |
||||||
|
} |
||||||
|
// Else ignore, we only do folder selection here
|
||||||
|
} |
||||||
|
|
||||||
|
private boolean hasPermission() { |
||||||
|
return runtimePermissionsHelper.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); |
||||||
|
} |
||||||
|
|
||||||
|
private void requestPermission() { |
||||||
|
runtimePermissionsHelper.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new RuntimePermissionsHelper.Callback() { |
||||||
|
@Override |
||||||
|
public void onRuntimePermissionResult(boolean granted) { |
||||||
|
gotPermission = granted; |
||||||
|
if (gotPermission) { |
||||||
|
initialize(); |
||||||
|
} else { |
||||||
|
new AlertDialog.Builder(context) |
||||||
|
.setTitle(R.string.write_permission_required_title) |
||||||
|
.setMessage(R.string.write_permission_required) |
||||||
|
.setCancelable(false) |
||||||
|
.setNeutralButton(R.string.write_permission_app_settings, new DialogInterface.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(DialogInterface dialog, int which) { |
||||||
|
requestPermission(); |
||||||
|
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, |
||||||
|
Uri.parse("package:" + context.getPackageName())); |
||||||
|
AndroidUtils.openIntent(intent); |
||||||
|
} |
||||||
|
}) |
||||||
|
.setPositiveButton(R.string.write_permission_grant, new DialogInterface.OnClickListener() { |
||||||
|
@Override |
||||||
|
public void onClick(DialogInterface dialog, int which) { |
||||||
|
requestPermission(); |
||||||
|
} |
||||||
|
}) |
||||||
|
.show(); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void initialize() { |
||||||
|
filesLayout.initialize(); |
||||||
|
fileWatcher.initialize(); |
||||||
|
} |
||||||
|
} |
@ -1,201 +0,0 @@ |
|||||||
/* |
|
||||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
|
||||||
* Copyright (C) 2014 Floens |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU 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 General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
package org.floens.chan.ui.fragment; |
|
||||||
|
|
||||||
import android.app.DialogFragment; |
|
||||||
import android.os.Bundle; |
|
||||||
import android.os.Environment; |
|
||||||
import android.view.LayoutInflater; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.widget.ArrayAdapter; |
|
||||||
import android.widget.FrameLayout; |
|
||||||
import android.widget.ListView; |
|
||||||
import android.widget.TextView; |
|
||||||
|
|
||||||
import org.floens.chan.R; |
|
||||||
import org.floens.chan.ui.theme.ThemeHelper; |
|
||||||
|
|
||||||
import java.io.File; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
public class FolderPickFragment extends DialogFragment { |
|
||||||
private TextView statusPath; |
|
||||||
private ListView listView; |
|
||||||
private ArrayAdapter<String> adapter; |
|
||||||
boolean hasParent = false; |
|
||||||
|
|
||||||
private FolderPickListener listener; |
|
||||||
private File currentPath; |
|
||||||
private List<File> directories; |
|
||||||
private FrameLayout okButton; |
|
||||||
private TextView okButtonIcon; |
|
||||||
|
|
||||||
public static FolderPickFragment newInstance(FolderPickListener listener, File startingPath) { |
|
||||||
FolderPickFragment fragment = new FolderPickFragment(); |
|
||||||
fragment.listener = listener; |
|
||||||
fragment.currentPath = startingPath; |
|
||||||
return fragment; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCreate(Bundle savedInstanceState) { |
|
||||||
super.onCreate(savedInstanceState); |
|
||||||
|
|
||||||
if (listener == null || currentPath == null) { |
|
||||||
dismiss(); |
|
||||||
} |
|
||||||
|
|
||||||
setStyle(STYLE_NO_TITLE, 0); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { |
|
||||||
if (listener == null || currentPath == null) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
View container = inflater.inflate(R.layout.fragment_folder_pick, parent); |
|
||||||
|
|
||||||
statusPath = (TextView) container.findViewById(R.id.folder_status); |
|
||||||
listView = (ListView) container.findViewById(R.id.folder_list); |
|
||||||
okButton = (FrameLayout) container.findViewById(R.id.pick_ok); |
|
||||||
okButtonIcon = (TextView) container.findViewById(R.id.pick_ok_icon); |
|
||||||
|
|
||||||
container.findViewById(R.id.pick_back).setOnClickListener(new View.OnClickListener() { |
|
||||||
@Override |
|
||||||
public void onClick(View v) { |
|
||||||
dismiss(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
okButton.findViewById(R.id.pick_ok).setOnClickListener(new View.OnClickListener() { |
|
||||||
@Override |
|
||||||
public void onClick(View v) { |
|
||||||
listener.folderPicked(currentPath); |
|
||||||
dismiss(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
if (!ThemeHelper.getInstance().getTheme().isLightTheme) { |
|
||||||
((TextView) container.findViewById(R.id.pick_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0); |
|
||||||
((TextView) container.findViewById(R.id.pick_ok_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_done_white_24dp, 0, 0, 0); |
|
||||||
} |
|
||||||
|
|
||||||
adapter = new ArrayAdapter<String>(inflater.getContext(), 0) { |
|
||||||
@Override |
|
||||||
public View getView(final int position, View convertView, ViewGroup parent) { |
|
||||||
if (convertView == null) { |
|
||||||
convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_1, null); |
|
||||||
} |
|
||||||
TextView text = (TextView) convertView; |
|
||||||
|
|
||||||
String name = getItem(position); |
|
||||||
|
|
||||||
text.setText(name); |
|
||||||
|
|
||||||
text.setOnClickListener(new View.OnClickListener() { |
|
||||||
@Override |
|
||||||
public void onClick(View v) { |
|
||||||
if (hasParent) { |
|
||||||
if (position == 0) { |
|
||||||
File parent = currentPath.getParentFile(); |
|
||||||
moveTo(parent); |
|
||||||
} else if (position > 0 && position <= directories.size()) { |
|
||||||
File dir = directories.get(position - 1); |
|
||||||
moveTo(dir); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (position >= 0 && position < directories.size()) { |
|
||||||
File dir = directories.get(position); |
|
||||||
moveTo(dir); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
return text; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
listView.setAdapter(adapter); |
|
||||||
|
|
||||||
if (currentPath == null || !currentPath.exists()) { |
|
||||||
currentPath = Environment.getExternalStorageDirectory(); |
|
||||||
} |
|
||||||
|
|
||||||
moveTo(currentPath); |
|
||||||
|
|
||||||
return container; |
|
||||||
} |
|
||||||
|
|
||||||
private boolean validPath(File path) { |
|
||||||
return path != null && path.isDirectory() && path.canRead() && path.canWrite(); |
|
||||||
} |
|
||||||
|
|
||||||
private void moveTo(File path) { |
|
||||||
if (path != null && path.isDirectory()) { |
|
||||||
File[] listFiles = path.listFiles(); |
|
||||||
if (listFiles != null) { |
|
||||||
currentPath = path; |
|
||||||
statusPath.setText(currentPath.getAbsolutePath()); |
|
||||||
List<File> dirs = new ArrayList<>(); |
|
||||||
for (File file : path.listFiles()) { |
|
||||||
if (file.isDirectory()) { |
|
||||||
dirs.add(file); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
setDirs(dirs); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
validState(); |
|
||||||
} |
|
||||||
|
|
||||||
private void validState() { |
|
||||||
if (validPath(currentPath)) { |
|
||||||
okButton.setEnabled(true); |
|
||||||
okButtonIcon.setEnabled(true); |
|
||||||
} else { |
|
||||||
okButton.setEnabled(false); |
|
||||||
okButtonIcon.setEnabled(false); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void setDirs(List<File> dirs) { |
|
||||||
directories = dirs; |
|
||||||
adapter.clear(); |
|
||||||
|
|
||||||
if (currentPath.getParent() != null) { |
|
||||||
adapter.add(".."); |
|
||||||
hasParent = true; |
|
||||||
} else { |
|
||||||
hasParent = false; |
|
||||||
} |
|
||||||
for (File file : dirs) { |
|
||||||
adapter.add(file.getName()); |
|
||||||
} |
|
||||||
adapter.notifyDataSetChanged(); |
|
||||||
} |
|
||||||
|
|
||||||
public interface FolderPickListener { |
|
||||||
void folderPicked(File path); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,141 @@ |
|||||||
|
package org.floens.chan.ui.layout; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.drawable.Drawable; |
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat; |
||||||
|
import android.support.v7.widget.LinearLayoutManager; |
||||||
|
import android.support.v7.widget.RecyclerView; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.LinearLayout; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
import org.floens.chan.R; |
||||||
|
import org.floens.chan.core.model.FileItem; |
||||||
|
import org.floens.chan.core.model.FileItems; |
||||||
|
import org.floens.chan.ui.adapter.FilesAdapter; |
||||||
|
import org.floens.chan.utils.RecyclerUtils; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import static org.floens.chan.utils.AndroidUtils.getAttrColor; |
||||||
|
|
||||||
|
public class FilesLayout extends LinearLayout implements FilesAdapter.Callback, View.OnClickListener { |
||||||
|
private ViewGroup backLayout; |
||||||
|
private ImageView backImage; |
||||||
|
private TextView backText; |
||||||
|
private RecyclerView recyclerView; |
||||||
|
|
||||||
|
private LinearLayoutManager layoutManager; |
||||||
|
private FilesAdapter filesAdapter; |
||||||
|
|
||||||
|
private Map<String, FileItemHistory> history = new HashMap<>(); |
||||||
|
private FileItemHistory currentHistory; |
||||||
|
private FileItems currentFileItems; |
||||||
|
|
||||||
|
private Callback callback; |
||||||
|
|
||||||
|
public FilesLayout(Context context) { |
||||||
|
this(context, null); |
||||||
|
} |
||||||
|
|
||||||
|
public FilesLayout(Context context, AttributeSet attrs) { |
||||||
|
this(context, attrs, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public FilesLayout(Context context, AttributeSet attrs, int defStyle) { |
||||||
|
super(context, attrs, defStyle); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onFinishInflate() { |
||||||
|
super.onFinishInflate(); |
||||||
|
backLayout = (ViewGroup) findViewById(R.id.back_layout); |
||||||
|
backImage = (ImageView) backLayout.findViewById(R.id.back_image); |
||||||
|
backImage.setImageDrawable(DrawableCompat.wrap(backImage.getDrawable())); |
||||||
|
backText = (TextView) backLayout.findViewById(R.id.back_text); |
||||||
|
recyclerView = (RecyclerView) findViewById(R.id.recycler); |
||||||
|
|
||||||
|
backLayout.setOnClickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void initialize() { |
||||||
|
layoutManager = new LinearLayoutManager(getContext()); |
||||||
|
recyclerView.setLayoutManager(layoutManager); |
||||||
|
|
||||||
|
filesAdapter = new FilesAdapter(this); |
||||||
|
recyclerView.setAdapter(filesAdapter); |
||||||
|
} |
||||||
|
|
||||||
|
public void setCallback(Callback callback) { |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFiles(FileItems fileItems) { |
||||||
|
// Save the associated list position
|
||||||
|
if (currentFileItems != null) { |
||||||
|
int[] indexTop = RecyclerUtils.getIndexAndTop(recyclerView); |
||||||
|
currentHistory.index = indexTop[0]; |
||||||
|
currentHistory.top = indexTop[1]; |
||||||
|
history.put(currentFileItems.path.getAbsolutePath(), currentHistory); |
||||||
|
} |
||||||
|
|
||||||
|
filesAdapter.setFiles(fileItems); |
||||||
|
currentFileItems = fileItems; |
||||||
|
|
||||||
|
// Restore any previous list position
|
||||||
|
currentHistory = history.get(fileItems.path.getAbsolutePath()); |
||||||
|
if (currentHistory != null) { |
||||||
|
layoutManager.scrollToPositionWithOffset(currentHistory.index, currentHistory.top); |
||||||
|
filesAdapter.setHighlightedItem(currentHistory.clickedItem); |
||||||
|
} else { |
||||||
|
currentHistory = new FileItemHistory(); |
||||||
|
filesAdapter.setHighlightedItem(null); |
||||||
|
} |
||||||
|
|
||||||
|
boolean enabled = fileItems.canNavigateUp; |
||||||
|
backLayout.setEnabled(enabled); |
||||||
|
Drawable wrapped = DrawableCompat.wrap(backImage.getDrawable()); |
||||||
|
backImage.setImageDrawable(wrapped); |
||||||
|
int color = getAttrColor(getContext(), enabled ? R.attr.text_color_primary : R.attr.text_color_hint); |
||||||
|
DrawableCompat.setTint(wrapped, color); |
||||||
|
backText.setEnabled(enabled); |
||||||
|
backText.setTextColor(color); |
||||||
|
} |
||||||
|
|
||||||
|
public RecyclerView getRecyclerView() { |
||||||
|
return recyclerView; |
||||||
|
} |
||||||
|
|
||||||
|
public ViewGroup getBackLayout() { |
||||||
|
return backLayout; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFileItemClicked(FileItem fileItem) { |
||||||
|
currentHistory.clickedItem = fileItem; |
||||||
|
callback.onFileItemClicked(fileItem); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClick(View view) { |
||||||
|
if (view == backLayout) { |
||||||
|
currentHistory.clickedItem = null; |
||||||
|
callback.onBackClicked(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class FileItemHistory { |
||||||
|
int index, top; |
||||||
|
FileItem clickedItem; |
||||||
|
} |
||||||
|
|
||||||
|
public interface Callback { |
||||||
|
void onBackClicked(); |
||||||
|
|
||||||
|
void onFileItemClicked(FileItem fileItem); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="24.0" |
||||||
|
android:viewportHeight="24.0"> |
||||||
|
<path |
||||||
|
android:fillColor="#FF000000" |
||||||
|
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/> |
||||||
|
</vector> |
@ -0,0 +1,9 @@ |
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="24.0" |
||||||
|
android:viewportHeight="24.0"> |
||||||
|
<path |
||||||
|
android:fillColor="#FF000000" |
||||||
|
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/> |
||||||
|
</vector> |
@ -0,0 +1,22 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="56dp" |
||||||
|
android:background="@drawable/item_background" |
||||||
|
android:orientation="horizontal"> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/image" |
||||||
|
android:layout_width="56dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:scaleType="center" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:padding="16dp" |
||||||
|
android:textSize="18sp" /> |
||||||
|
|
||||||
|
</LinearLayout> |
@ -0,0 +1,67 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="?backcolor"> |
||||||
|
|
||||||
|
<org.floens.chan.ui.layout.FilesLayout |
||||||
|
android:id="@+id/files_layout" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:clipToPadding="false" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:background="?backcolor" |
||||||
|
android:elevation="4dp"> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:id="@+id/back_layout" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="56dp" |
||||||
|
android:layout_gravity="left" |
||||||
|
android:background="@drawable/item_background" |
||||||
|
android:elevation="8dp" |
||||||
|
android:orientation="horizontal" |
||||||
|
android:paddingRight="22dp"> |
||||||
|
|
||||||
|
<ImageView |
||||||
|
android:id="@+id/back_image" |
||||||
|
android:layout_width="56dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:scaleType="center" |
||||||
|
android:src="@drawable/ic_chevron_left_black_24dp" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/back_text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:text="@string/up" |
||||||
|
android:textSize="18sp" /> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
</FrameLayout> |
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView |
||||||
|
android:id="@+id/recycler" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:layout_weight="1" |
||||||
|
android:clipChildren="false" |
||||||
|
android:clipToPadding="false" /> |
||||||
|
|
||||||
|
</org.floens.chan.ui.layout.FilesLayout> |
||||||
|
|
||||||
|
<android.support.design.widget.FloatingActionButton |
||||||
|
android:id="@+id/set_button" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="right|bottom" |
||||||
|
android:layout_margin="16dp" |
||||||
|
android:src="@drawable/ic_done_white_24dp" /> |
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout> |
Loading…
Reference in new issue