diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java index dec214bbbb19bf163d760eff9a1935697e524dd4..f4d9ac2a5907b4c9e1ee34edb592e94a4a2009fa 100644 --- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java @@ -69,6 +69,7 @@ import cx.ring.services.NotificationService; import cx.ring.settings.SettingsFragment; import cx.ring.settings.VideoSettingsFragment; import cx.ring.settings.pluginssettings.PluginDetails; +import cx.ring.settings.pluginssettings.PluginPathPreferenceFragment; import cx.ring.settings.pluginssettings.PluginSettingsFragment; import cx.ring.settings.pluginssettings.PluginsListSettingsFragment; import cx.ring.utils.ContentUriHandler; @@ -104,6 +105,7 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV public static final String PLUGINS_LIST_SETTINGS_TAG = "PluginsListSettings"; public static final String PLUGIN_SETTINGS_TAG = "PluginSettings"; + public static final String PLUGIN_PATH_PREFERENCE_TAG = "PluginPathPreference"; private static final String NAVIGATION_TAG = "Navigation"; @@ -660,7 +662,7 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .replace(R.id.main_frame, fContent, PLUGINS_LIST_SETTINGS_TAG) + .replace(getFragmentContainerId(), fContent, PLUGINS_LIST_SETTINGS_TAG) .addToBackStack(PLUGINS_LIST_SETTINGS_TAG).commit(); } @@ -676,10 +678,25 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .replace(R.id.main_frame, fContent, PLUGIN_SETTINGS_TAG) + .replace(getFragmentContainerId(), fContent, PLUGIN_SETTINGS_TAG) .addToBackStack(PLUGIN_SETTINGS_TAG).commit(); } + /** + * Changes the current main fragment to a plugin PATH preference fragment + */ + public void gotToPluginPathPreference(PluginDetails pluginDetails, String preferenceKey){ + if (fContent instanceof PluginPathPreferenceFragment) { + return; + } + fContent = PluginPathPreferenceFragment.newInstance(pluginDetails, preferenceKey); + getSupportFragmentManager() + .beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(getFragmentContainerId(), fContent, PLUGIN_PATH_PREFERENCE_TAG) + .addToBackStack(PLUGIN_PATH_PREFERENCE_TAG).commit(); + } + @Override public void setColor(int color) { //mToolbar.setBackground(new ColorDrawable(color)); diff --git a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java index d68bfff394a7d68caadd930d5f12d0757d878144..d2166a26b74a48d721fe60387b35362325424cbe 100644 --- a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java +++ b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java @@ -130,16 +130,14 @@ public class PluginUtils { */ public static String listStringToStringList(List<String> listString) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append('['); if(!listString.isEmpty()) { - for(int i=0; i< listString.size()-1; i++){ + for(int i=0; i< listString.size(); i++){ stringBuilder.append(listString.get(i)).append(","); } - stringBuilder.append(listString.get(listString.size()-1)); + stringBuilder.append(listString.get(listString.size())); } - stringBuilder.append(']'); return stringBuilder.toString(); } @@ -153,7 +151,7 @@ public class PluginUtils { List<String> listString = new ArrayList<>(); StringBuilder currentWord = new StringBuilder(); if(!stringList.isEmpty()) { - for (int i = 1; i < stringList.length() - 1; i++) { + for (int i = 0; i < stringList.length(); i++) { char currentChar = stringList.charAt(i); if (currentChar != ',') { currentWord.append(currentChar); @@ -162,7 +160,7 @@ public class PluginUtils { currentWord = new StringBuilder(); } - if (i == stringList.length() - 2) { + if (i == stringList.length() - 1) { listString.add(currentWord.toString()); break; } diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..313c6c074de9afac56df7d9b27c64bcf4518dc5b --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * 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 cx.ring.settings.pluginssettings; + +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.File; +import java.util.List; + +import cx.ring.R; + +import static cx.ring.utils.AndroidFileUtils.getFileName; +import static cx.ring.utils.AndroidFileUtils.isImage; + + +public class PathListAdapter extends RecyclerView.Adapter<PathListAdapter.PathViewHolder> { + private List<String> mList; + private PathListItemListener listener; + private Drawable icon; + public static final String TAG = PathListAdapter.class.getSimpleName(); + + PathListAdapter(List<String> pathList, PathListItemListener listener) { + this.mList = pathList; + this.listener = listener; + } + + + @NonNull + @Override + public PathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.frag_path_list_item, parent, false); + + return new PathViewHolder(view, listener); + } + + @Override + public void onBindViewHolder(@NonNull PathViewHolder holder, int position) { + holder.setDetails(mList.get(position)); + } + + @Override + public int getItemCount() { + return mList.size(); + } + + public void updatePluginsList(List<String> listPaths) { + mList = listPaths; + notifyDataSetChanged(); + } + + class PathViewHolder extends RecyclerView.ViewHolder{ + private final ImageView pathIcon; + private final TextView pathTextView; + private String path; + + PathViewHolder(@NonNull View itemView, PathListItemListener listener) { + super(itemView); + // Views that should be updated by the update method + pathIcon = itemView.findViewById(R.id.path_item_icon); + pathTextView = itemView.findViewById(R.id.path_item_name); + + // Set listeners, we set the listeners on creation so details can be null + itemView.setOnClickListener(v -> listener.onPathItemClicked(path)); + } + + // update the viewHolder view + public void update(String s) { + // Set the plugin icon + File file = new File(s); + if (file.exists()) { + if (isImage(s)) { + pathTextView.setVisibility(View.GONE); + icon = Drawable.createFromPath(s); + if (icon != null) { + pathIcon.setImageDrawable(icon); + } + } else { + pathTextView.setVisibility(View.VISIBLE); + pathTextView.setText(getFileName(s)); + } + } + } + + public void setDetails(String path) { + this.path = path; + update(this.path); + } + } + + public interface PathListItemListener { + void onPathItemClicked(String path); + } +} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java index 558744dfd81c3c4a05ef29b4139d800bda9a842c..c27c9bd77ad6a151514da1021913710ad43fffb5 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * 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 cx.ring.settings.pluginssettings; import android.graphics.drawable.Drawable; @@ -7,7 +25,6 @@ import java.util.List; import java.util.Map; import cx.ring.daemon.Ringservice; -import cx.ring.utils.Log; /** * Class that contains PluginDetails like name, rootPath @@ -16,7 +33,7 @@ public class PluginDetails { public static final String TAG = PluginDetails.class.getSimpleName(); private String name; private String rootPath; - private Map<String, String> details; + private final Map<String, String> details; private Drawable icon; private boolean enabled; @@ -40,17 +57,6 @@ public class PluginDetails { return rootPath; } - /** - * @return String: absolute path to the so file - */ - public String getSoPath() { - return details.get("soPath") != null ? details.get("soPath") : ""; - } - - public void setRootPath(String rootPath) { - this.rootPath = rootPath; - } - /** * Returns the plugin activation status by the user * @return boolean @@ -65,12 +71,8 @@ public class PluginDetails { public void setIcon() { String iconPath = details.get("iconPath"); - - if(iconPath != null) { + if (iconPath != null) { File file = new File(iconPath); - - Log.i(TAG, "Plugin icon path: " + iconPath); - if(file.exists()) { icon = Drawable.createFromPath(iconPath); } @@ -90,7 +92,7 @@ public class PluginDetails { } public Map<String, String> getPluginPreferencesValues() { - return Ringservice.getPluginPreferencesValues(getRootPath()).toNative(); + return Ringservice.getPluginPreferencesValues(getRootPath()); } public boolean setPluginPreference(String key, String value) { diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..96a279ffd48590c70568c5eb4c0715597728eee8 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * 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 cx.ring.settings.pluginssettings; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import cx.ring.R; +import cx.ring.client.HomeActivity; +import cx.ring.databinding.FragPluginsPathPreferenceBinding; +import cx.ring.utils.AndroidFileUtils; +import io.reactivex.android.schedulers.AndroidSchedulers; + +public class PluginPathPreferenceFragment extends Fragment implements PathListAdapter.PathListItemListener { + + public static final String TAG = PluginPathPreferenceFragment.class.getSimpleName(); + private static final int PATH_REQUEST_CODE = 1; + private final List<String> pathList = new ArrayList<>(); + private PluginDetails mPluginDetails; + private String mCurrentKey; + private String mCurrentValue; + private Context mContext; + private String subtitle; + private String supportedMimeTypes = "*/*"; + + private FragPluginsPathPreferenceBinding binding; + + public static PluginPathPreferenceFragment newInstance(PluginDetails pluginDetails, String preferenceKey) { + Bundle args = new Bundle(); + args.putString("name", pluginDetails.getName()); + args.putString("rootPath", pluginDetails.getRootPath()); + args.putBoolean("enabled", pluginDetails.isEnabled()); + args.putString("preferenceKey", preferenceKey); + PluginPathPreferenceFragment ppf = new PluginPathPreferenceFragment(); + ppf.setArguments(args); + return ppf; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = requireActivity(); + + Bundle arguments = getArguments(); + + if (arguments != null && arguments.getString("name") != null + && arguments.getString("rootPath") != null) { + mPluginDetails = new PluginDetails(arguments.getString("name"), arguments.getString("rootPath"), arguments.getBoolean("enabled")); + + List<Map<String, String>> mPreferencesAttributes = mPluginDetails.getPluginPreferences(); + if (mPreferencesAttributes != null && !mPreferencesAttributes.isEmpty()) { + mCurrentKey = arguments.getString("preferenceKey"); + mCurrentValue = mPluginDetails.getPluginPreferencesValues().get(mCurrentKey); + setHasOptionsMenu(true); + for (Map<String, String> preferenceAttributes : mPreferencesAttributes) { + if (preferenceAttributes.get("key").equals(mCurrentKey)) { + String mimeType = preferenceAttributes.get("mimeType"); + if (!TextUtils.isEmpty(mimeType)) + supportedMimeTypes = mimeType; + subtitle = mPluginDetails.getName() + " - " + preferenceAttributes.get("title"); + + String defaultPath = preferenceAttributes.get("defaultValue"); + if (!TextUtils.isEmpty(defaultPath)) { + defaultPath = defaultPath.substring(0, defaultPath.lastIndexOf("/")); + for (File file : new File(defaultPath).listFiles()) { + pathList.add(file.toString()); + } + break; + } + } + } + } + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragPluginsPathPreferenceBinding.inflate(inflater, container, false); + + if (!pathList.isEmpty()) { + binding.pathPreferences.setAdapter(new PathListAdapter(pathList, this)); + } + + ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + + binding.pluginSettingSubtitle.setText(subtitle); + binding.pluginsPathPreferenceFab.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(supportedMimeTypes); + startActivityForResult(intent, PATH_REQUEST_CODE); + }); + + if (!mCurrentValue.isEmpty()) { + binding.currentPathItemIcon.setVisibility(View.VISIBLE); + File file = new File(mCurrentValue); + if (file.exists()) { + if (AndroidFileUtils.isImage(mCurrentValue)) { + binding.currentPathItemName.setVisibility(View.INVISIBLE); + Drawable icon = Drawable.createFromPath(mCurrentValue); + if (icon != null) { + binding.currentPathItemIcon.setImageDrawable(icon); + } + } else { + binding.currentPathItemName.setVisibility(View.VISIBLE); + binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue)); + } + } + } else { + binding.currentPathItemIcon.setVisibility(View.INVISIBLE); + binding.currentPathItemName.setVisibility(View.INVISIBLE); + binding.pluginsPathPreferenceFab.performClick(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PATH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + if (data != null) { + Uri uri = data.getData(); + if (uri != null) { + AndroidFileUtils.getCacheFile(requireContext(), uri) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(file -> setPreferencePath(file.getAbsolutePath()), + e -> Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_LONG).show()); + } + } + } + } + + @Override + public void onResume() { + ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list); + super.onResume(); + } + + @Override + public void onDestroyView() { + binding = null; + super.onDestroyView(); + } + + private void setPreferencePath(String path) { + if (mPluginDetails.setPluginPreference(mCurrentKey, path)) { + mCurrentValue = path; + if (!path.isEmpty()) { + binding.currentPathItemIcon.setVisibility(View.VISIBLE); + if (AndroidFileUtils.isImage(mCurrentValue)) { + Drawable icon = Drawable.createFromPath(mCurrentValue); + binding.currentPathItemIcon.setImageDrawable(icon); + binding.currentPathItemName.setVisibility(View.INVISIBLE); + } else { + binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue)); + binding.currentPathItemName.setVisibility(View.VISIBLE); + } + } else { + binding.currentPathItemIcon.setVisibility(View.INVISIBLE); + binding.currentPathItemName.setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onPathItemClicked(String path) { + setPreferencePath(path); + } +} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java index 665c2284ec44aebbdfff5447d2e884d231f4b38f..f7db21ae3e54094d529b3b866eb8daaa9399c41d 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceDataStore; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -17,6 +18,7 @@ import static cx.ring.plugins.PluginUtils.stringListToListString; public class PluginPreferencesDataStore extends PreferenceDataStore { private PluginDetails mPluginDetails; + private Map<String, String> mPreferenceTypes = new HashMap<>(); private Map<String, String> preferencesValues; private ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -25,6 +27,16 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { preferencesValues = mPluginDetails.getPluginPreferencesValues(); } + public void addTomPreferenceTypes(Map<String, String> preferenceModel) { + Lock writeLock = lock.writeLock(); + try { + writeLock.lock(); + mPreferenceTypes.put(preferenceModel.get("key"), preferenceModel.get("type")); + } finally { + writeLock.unlock(); + } + } + @Override public void putBoolean(String key, boolean value) { Boolean boxedValue = value; @@ -111,7 +123,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { int returnValue = defValue; String value = getPreferencesValues().get(key); if (value != null) { - returnValue = Integer.valueOf(value); + returnValue = Integer.parseInt(value); } return returnValue; } @@ -121,7 +133,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { long returnValue = defValue; String value = getPreferencesValues().get(key); if (value != null) { - returnValue = Long.valueOf(value); + returnValue = Long.parseLong(value); } return returnValue; } @@ -131,7 +143,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { float returnValue = defValue; String value = getPreferencesValues().get(key); if (value != null) { - returnValue = Float.valueOf(value); + returnValue = Float.parseFloat(value); } return returnValue; } @@ -141,7 +153,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { boolean returnValue = defValue; String value = getPreferencesValues().get(key); if (value != null) { - returnValue = Boolean.valueOf(value); + returnValue = Boolean.parseBoolean(value); } return returnValue; } @@ -150,7 +162,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore { * Updates the preferencesValues map * Use locks since the PreferenceInteraction is asynchronous */ - private void notifyPreferencesValuesChange() { + public void notifyPreferencesValuesChange() { Lock writeLock = lock.writeLock(); try { writeLock.lock(); diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java index 95da3ec620221f7271b6b4384edc7893e1cc077a..63b27035fac57c588aad167bbb3463a90fa1652e 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java @@ -1,7 +1,6 @@ package cx.ring.settings.pluginssettings; import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -13,7 +12,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import androidx.preference.CheckBoxPreference; import androidx.preference.DialogPreference; import androidx.preference.DropDownPreference; @@ -35,15 +33,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.prefs.Preferences; import cx.ring.R; import cx.ring.client.HomeActivity; import cx.ring.daemon.Ringservice; -import cx.ring.plugins.ButtonPreference.ButtonPreference; import cx.ring.plugins.PluginPreferences; -import static android.content.Context.MODE_PRIVATE; import static cx.ring.plugins.PluginUtils.getOrElse; import static cx.ring.plugins.PluginUtils.stringListToListString; @@ -52,6 +47,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { private Context mContext; private List<Map<String, String>> mPreferencesAttributes; private PluginDetails pluginDetails; + private PluginPreferencesDataStore ppds; public static PluginSettingsFragment newInstance(PluginDetails pluginDetails) { Bundle args = new Bundle(); @@ -83,7 +79,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { mPreferencesAttributes = pluginDetails.getPluginPreferences(); PreferenceManager preferenceManager = getPreferenceManager(); - PluginPreferencesDataStore ppds = new PluginPreferencesDataStore(pluginDetails); + ppds = new PluginPreferencesDataStore(pluginDetails); preferenceManager.setPreferenceDataStore(ppds); setHasOptionsMenu(true); } @@ -138,6 +134,9 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { case "List": preferencesViews.add(createListPreference(preferenceAttributes)); break; + case "Path": + preferencesViews.add(createPathPreference(preferenceAttributes)); + break; case "MultiSelectList": preferencesViews. add(createMultiSelectListPreference(preferenceAttributes)); @@ -161,37 +160,27 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { private Preference createHeadPreference() { PluginPreferences preference = new PluginPreferences(mContext, pluginDetails); - preference.setResetClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - new MaterialAlertDialogBuilder(mContext) - .setTitle(preference.getTitle()) - .setMessage(R.string.plugin_reset_preferences_ask) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - Ringservice.resetPluginPreferencesValues(pluginDetails.getRootPath()); - ((HomeActivity) requireActivity()).popFragmentImmediate(); - }) - .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { - /* Terminate with no action */ - }) - .show(); - } - }); - preference.setInstallClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - new MaterialAlertDialogBuilder(mContext) - .setMessage(R.string.account_delete_dialog_message) - .setTitle(R.string.plugin_uninstall_title) - .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { - pluginDetails.setEnabled(false); - Ringservice.uninstallPlugin(pluginDetails.getRootPath()); - ((HomeActivity) requireActivity()).popFragmentImmediate(); - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - } - }); + preference.setResetClickListener(v -> new MaterialAlertDialogBuilder(mContext) + .setTitle(preference.getTitle()) + .setMessage(R.string.plugin_reset_preferences_ask) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + Ringservice.resetPluginPreferencesValues(pluginDetails.getRootPath()); + ((HomeActivity) requireActivity()).popFragmentImmediate(); + }) + .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { + /* Terminate with no action */ + }) + .show()); + preference.setInstallClickListener(v -> new MaterialAlertDialogBuilder(mContext) + .setMessage(R.string.account_delete_dialog_message) + .setTitle(R.string.plugin_uninstall_title) + .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { + pluginDetails.setEnabled(false); + Ringservice.uninstallPlugin(pluginDetails.getRootPath()); + ((HomeActivity) requireActivity()).popFragmentImmediate(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show()); return preference; } @@ -199,6 +188,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { CheckBoxPreference preference = new CheckBoxPreference(mContext); setPreferenceAttributes(preference, preferenceModel); setTwoStatePreferenceAttributes(preference, preferenceModel); + ppds.addTomPreferenceTypes(preferenceModel); return preference; } @@ -206,6 +196,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { DropDownPreference preference = new DropDownPreference(mContext); setPreferenceAttributes(preference, preferenceModel); setListPreferenceAttributes(preference, preferenceModel); + ppds.addTomPreferenceTypes(preferenceModel); return preference; } @@ -214,6 +205,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { setPreferenceAttributes(preference, preferenceModel); setDialogPreferenceAttributes(preference, preferenceModel); setEditTextAttributes(preference, preferenceModel); + ppds.addTomPreferenceTypes(preferenceModel); return preference; } @@ -221,6 +213,18 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { ListPreference preference = new ListPreference(mContext); setPreferenceAttributes(preference, preferenceModel); setListPreferenceAttributes(preference, preferenceModel); + ppds.addTomPreferenceTypes(preferenceModel); + return preference; + } + + private Preference createPathPreference(Map<String, String> preferenceModel){ + Preference preference = new Preference(mContext); + preference.setOnPreferenceClickListener(p -> { + ((HomeActivity) mContext).gotToPluginPathPreference(pluginDetails, preferenceModel.get("key")); + return false; + }); + setPreferenceAttributes(preference, preferenceModel); + ppds.addTomPreferenceTypes(preferenceModel); return preference; } @@ -307,10 +311,10 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { private void setListPreferenceAttributes(ListPreference preference, Map<String, String> preferenceModel) { setDialogPreferenceAttributes(preference, preferenceModel); - String entries = getOrElse(preferenceModel.get("entries"), "[]"); + String entries = getOrElse(preferenceModel.get("entries"), ""); preference.setEntries(stringListToListString(entries). toArray(new CharSequence[ 0 ])); - String entryValues = getOrElse(preferenceModel.get("entryValues"), "[]"); + String entryValues = getOrElse(preferenceModel.get("entryValues"), ""); preference.setEntryValues(stringListToListString(entryValues). toArray(new CharSequence[ 0 ])); preference.setDefaultValue(preferenceModel.get("defaultValue")); @@ -360,8 +364,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { preference.setMin(min); preference.setMax(max); preference.setSeekBarIncrement(increment); - preference.setAdjustable(Boolean.valueOf(getOrElse(preferenceModel.get("adjustable"), - "true"))); + preference.setAdjustable(Boolean.parseBoolean(getOrElse(preferenceModel.get("adjustable"), "true"))); preference.setDefaultValue(defaultValue); preference.setShowSeekBarValue(true); preference.setUpdatesContinuously(true); diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java index 8e0a1734356b7451bf35cf2d3aaaa1bfeb3664ec..9bbfc74fcdca9c9a5a2928d149da4122bfd934f5 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java @@ -1,7 +1,6 @@ package cx.ring.settings.pluginssettings; import android.graphics.drawable.Drawable; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,10 +15,9 @@ import java.util.List; import cx.ring.R; - public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter.PluginViewHolder> { private List<PluginDetails> mList; - private PluginListItemListener listener; + private final PluginListItemListener listener; public static final String TAG = PluginsListAdapter.class.getSimpleName(); PluginsListAdapter(List<PluginDetails> pluginsList, PluginListItemListener listener) { @@ -27,13 +25,10 @@ public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter. this.listener = listener; } - @NonNull @Override public PluginViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.frag_plugins_list_item, parent, false); - + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.frag_plugins_list_item, parent, false); return new PluginViewHolder(view, listener); } @@ -49,14 +44,13 @@ public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter. public void updatePluginsList(List<PluginDetails> listPlugins) { mList = listPlugins; - notifyDataSetChanged(); } - class PluginViewHolder extends RecyclerView.ViewHolder{ - private ImageView pluginIcon; - private TextView pluginNameTextView; - private CheckBox pluginItemEnableCheckbox; + static class PluginViewHolder extends RecyclerView.ViewHolder{ + private final ImageView pluginIcon; + private final TextView pluginNameTextView; + private final CheckBox pluginItemEnableCheckbox; private PluginDetails details = null; PluginViewHolder(@NonNull View itemView, PluginListItemListener listener) { @@ -68,19 +62,17 @@ public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter. // Set listeners, we set the listeners on creation so details can be null itemView.setOnClickListener(v -> { - Log.i(TAG, "CLICK"); if (details != null) { listener.onPluginItemClicked(details); } }); - pluginItemEnableCheckbox.setOnClickListener( - v -> { - if (details != null) { - this.details.setEnabled(!this.details.isEnabled()); - listener.onPluginEnabled(details); - } - }); + pluginItemEnableCheckbox.setOnClickListener(v -> { + if (details != null) { + details.setEnabled(!details.isEnabled()); + listener.onPluginEnabled(details); + } + }); } public void setDetails(PluginDetails details) { @@ -94,7 +86,7 @@ public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter. pluginItemEnableCheckbox.setChecked(details.isEnabled()); // Set the plugin icon Drawable icon = details.getIcon(); - if(icon != null) { + if (icon != null) { pluginIcon.setImageDrawable(icon); } } diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java index c72ffa9c9bf97cba5b1b7040f02ef4cf79d90d72..38621a4e07cf129f22738c947709336c9b2b7a7b 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java @@ -1,9 +1,7 @@ package cx.ring.settings.pluginssettings; import android.app.Activity; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.util.Log; @@ -14,12 +12,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import java.io.File; @@ -29,12 +23,11 @@ import java.util.List; import cx.ring.R; import cx.ring.client.HomeActivity; import cx.ring.daemon.Ringservice; +import cx.ring.databinding.FragPluginsListSettingsBinding; import cx.ring.plugins.PluginUtils; import cx.ring.utils.AndroidFileUtils; -import io.reactivex.SingleObserver; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; import static cx.ring.plugins.PluginUtils.listAvailablePlugins; @@ -43,57 +36,38 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList public static final String TAG = PluginsListSettingsFragment.class.getSimpleName(); private static final int ARCHIVE_REQUEST_CODE = 42; - private Context mContext; - private CoordinatorLayout mCoordinatorLayout; - private RecyclerView mRecyclerView; - private RecyclerView.Adapter mAdapter; - private FloatingActionButton fab; - private CompositeDisposable mCompositeDisposable = new CompositeDisposable();; + private FragPluginsListSettingsBinding binding; + private PluginsListAdapter mAdapter; + private final CompositeDisposable mCompositeDisposable = new CompositeDisposable(); @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - mContext = requireActivity(); + binding = FragPluginsListSettingsBinding.inflate(inflater, container, false); - View pluginsSettingsList = inflater.inflate(R.layout.frag_plugins_list_settings, - container, false); - mCoordinatorLayout = pluginsSettingsList. - findViewById(R.id.plugins_list_settings_coordinator_layout); - mRecyclerView = pluginsSettingsList.findViewById(R.id.plugins_list); // use this setting to improve performance if you know that changes // in content do not change the layout size of the RecyclerView - mRecyclerView.setHasFixedSize(true); - - // use a linear layout manager - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext); - mRecyclerView.setLayoutManager(layoutManager); + binding.pluginsList.setHasFixedSize(true); // specify an adapter (see also next example) - - mAdapter = new PluginsListAdapter(listAvailablePlugins(mContext), this); - mRecyclerView.setAdapter(mAdapter); + mAdapter = new PluginsListAdapter(listAvailablePlugins(binding.pluginsList.getContext()), this); + binding.pluginsList.setAdapter(mAdapter); //Fab - fab = pluginsSettingsList.findViewById(R.id.plugins_list_settings_fab); - - fab.setOnClickListener(view -> { + binding.pluginsListSettingsFab.setOnClickListener(view -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); startActivityForResult(intent, ARCHIVE_REQUEST_CODE); }); - ((HomeActivity) requireActivity()). - setToolbarState(R.string.menu_item_plugin_list); - - return pluginsSettingsList; + return binding.getRoot(); } @Override public void onResume() { - ((HomeActivity) requireActivity()). - setToolbarState(R.string.menu_item_plugin_list); + ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list); super.onResume(); } @@ -104,7 +78,7 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList */ @Override public void onPluginItemClicked(PluginDetails pluginDetails) { - ((HomeActivity) mContext).gotToPluginSettings(pluginDetails); + ((HomeActivity) requireActivity()).gotToPluginSettings(pluginDetails); } /** @@ -121,7 +95,7 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList PluginUtils.unloadPlugin(pluginDetails.getRootPath()); } String status = pluginDetails.isEnabled()?"ON":"OFF"; - Toast.makeText(mContext,pluginDetails.getName() + " " + status, + Toast.makeText(requireContext(), pluginDetails.getName() + " " + status, Toast.LENGTH_SHORT).show(); } @@ -131,9 +105,6 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList if(data != null) { Uri uri = data.getData(); if (uri != null) { - if(mCompositeDisposable.isDisposed()){ - mCompositeDisposable = new CompositeDisposable(); - } installPluginFromUri(uri, false); } } @@ -142,10 +113,7 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList private String installPluginFile(File pluginFile, boolean force) throws IOException{ int i = Ringservice.installPlugin(pluginFile.getAbsolutePath(), force); - - // Free the cache - boolean cacheFileFreed = pluginFile.delete(); - if(!cacheFileFreed) { + if (!pluginFile.delete()) { Log.e(TAG,"Plugin Jpl file in the cache not freed"); } switch (i) { @@ -167,46 +135,34 @@ public class PluginsListSettingsFragment extends Fragment implements PluginsList } private void installPluginFromUri(Uri uri, boolean force) { - AndroidFileUtils.getCacheFile(requireContext(), uri) + mCompositeDisposable.add(AndroidFileUtils.getCacheFile(requireContext(), uri) .observeOn(AndroidSchedulers.mainThread()) .map(file -> installPluginFile(file, force)) - .subscribe(new SingleObserver<String>() { - @Override - public void onSubscribe(Disposable d) { - mCompositeDisposable.add(d); - } - - @Override - public void onSuccess(String filename) { - + .subscribe(filename -> { String[] plugin = filename.split(".jpl"); - List<PluginDetails> availablePlugins = listAvailablePlugins(mContext); - for (PluginDetails availablePlugin : availablePlugins){ - if (availablePlugin.getName().equals(plugin[0])) - { + List<PluginDetails> availablePlugins = listAvailablePlugins(requireContext()); + for (PluginDetails availablePlugin : availablePlugins) { + if (availablePlugin.getName().equals(plugin[0])) { availablePlugin.setEnabled(true); onPluginEnabled(availablePlugin); } } - ((PluginsListAdapter) mAdapter) - .updatePluginsList(listAvailablePlugins(mContext)); - Toast.makeText(mContext, "Plugin: " + filename + - " successfully installed", Toast.LENGTH_LONG).show(); + mAdapter.updatePluginsList(listAvailablePlugins(requireContext())); + Toast.makeText(requireContext(), "Plugin: " + filename + " successfully installed", Toast.LENGTH_LONG).show(); mCompositeDisposable.dispose(); - } - - @Override - public void onError(Throwable e) { - Snackbar sb = Snackbar.make(mCoordinatorLayout, - "" + - e.getMessage(), Snackbar.LENGTH_LONG); - sb.setAction(R.string.plugin_force_install, - v -> installPluginFromUri(uri, true)); - - sb.show(); - mCompositeDisposable.clear(); - } - }); + }, e -> { + if (binding != null) { + Snackbar sb = Snackbar.make(binding.listLayout, "" + e.getMessage(), Snackbar.LENGTH_LONG); + sb.setAction(R.string.plugin_force_install, v -> installPluginFromUri(uri, true)); + sb.show(); + } + })); + } + + @Override + public void onDestroyView() { + binding = null; + super.onDestroyView(); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java index eb4eb50fb9bf61da26c114c5d6b0cfde87dea1d0..1a17a1a0990f288ad39114c4dc34f6b5f546f5a8 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java +++ b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java @@ -564,4 +564,12 @@ public class AndroidFileUtils { } } + public static boolean isImage(String s) { + return getMimeType(s).startsWith("image"); + } + + public static String getFileName(String s) { + String[] parts = s.split("\\/"); + return parts[parts.length - 1]; + } } diff --git a/ring-android/app/src/main/res/layout/frag_path_list_item.xml b/ring-android/app/src/main/res/layout/frag_path_list_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..375d06a27a1e9c2399cb692248bfa76c05e1688f --- /dev/null +++ b/ring-android/app/src/main/res/layout/frag_path_list_item.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="4dp"> + + <TextView + android:id="@+id/path_item_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="File.ext" + android:textAlignment="center" + android:textSize="16sp" + app:layout_constraintTop_toBottomOf="@+id/path_item_icon" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:visibility="gone"/> + + <ImageView + android:id="@+id/path_item_icon" + android:layout_width="70dp" + android:layout_height="70dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/baseline_insert_drive_file_24" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_plugins_list_settings.xml b/ring-android/app/src/main/res/layout/frag_plugins_list_settings.xml index c20a296172fafab39c9b5fe25e39b7024a0db042..6b1d4aff09e9bde848e7fe7dceb0c75ec9a6cd02 100644 --- a/ring-android/app/src/main/res/layout/frag_plugins_list_settings.xml +++ b/ring-android/app/src/main/res/layout/frag_plugins_list_settings.xml @@ -1,22 +1,26 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.coordinatorlayout.widget.CoordinatorLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/plugins_list_settings_coordinator_layout" - android:layout_height="match_parent" - android:layout_width="match_parent"> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/list_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/plugins_list" android:layout_width="match_parent" - android:layout_height="match_parent"> - </androidx.recyclerview.widget.RecyclerView> + android:layout_height="match_parent" + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/frag_plugins_list_item"/> <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/plugins_list_settings_fab" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end|bottom" - android:src="@drawable/baseline_add_24" - android:contentDescription="@string/fab_plugins_add" - android:layout_margin="@dimen/fab_margin" /> + android:id="@+id/plugins_list_settings_fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|bottom" + android:layout_margin="@dimen/fab_margin" + android:contentDescription="@string/fab_plugins_add" + android:src="@drawable/baseline_add_24" /> -</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file +</FrameLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_plugins_path_preference.xml b/ring-android/app/src/main/res/layout/frag_plugins_path_preference.xml new file mode 100644 index 0000000000000000000000000000000000000000..b98027b7163d51cfd4901923225f1693942b5952 --- /dev/null +++ b/ring-android/app/src/main/res/layout/frag_plugins_path_preference.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/path_preference_coordinator_layout" + android:layout_height="match_parent" + android:layout_width="match_parent"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextView + android:id="@+id/plugin_setting_subtitle" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + tools:text="Plugin Name + Preference Name" + android:layout_marginHorizontal="16dp" + android:textSize="18sp"/> + + <ImageView + android:id="@+id/current_path_item_icon" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_above="@+id/current_path_item_name" + android:orientation="vertical" + android:gravity="center" + android:layout_below="@+id/plugin_setting_subtitle" + app:srcCompat="@drawable/baseline_insert_drive_file_24"> + + </ImageView> + + <TextView + android:id="@+id/current_path_item_name" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + tools:text="File.ext" + android:layout_above="@+id/path_preferences" + android:textAlignment="center" + android:textSize="16sp" /> + + <androidx.recyclerview.widget.RecyclerView + android:layout_toEndOf="@+id/plugins_path_preference_fab" + android:layout_alignParentBottom="true" + android:id="@+id/path_preferences" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation = "horizontal" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/frag_path_list_item"/> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/plugins_path_preference_fab" + android:layout_width="55dp" + android:layout_height="55dp" + android:gravity="center" + android:layout_alignParentStart="true" + android:layout_alignParentBottom="true" + android:layout_alignBaseline="@+id/path_preferences" + android:src="@drawable/baseline_add_24" + android:layout_margin="16dp" + android:contentDescription="@string/fab_plugins_add" /> + </RelativeLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml index 9d2f386805b90874a044ce1b14b46242b6e97431..d5bd7240aa4162b3247dd65828833e711a358c1a 100644 --- a/ring-android/app/src/main/res/values/strings.xml +++ b/ring-android/app/src/main/res/values/strings.xml @@ -364,5 +364,6 @@ along with this program; if not, write to the Free Software <string name="wizard_profile_info">Profile is only shared with contacts</string> <string name="wizard_profile_button">Save Profile</string> <string name="wizard_profile_skip">Skip and do this later</string> + <string name="fab_path_choose">Choose another file</string> </resources> diff --git a/ring-android/app/src/main/res/values/strings_preferences.xml b/ring-android/app/src/main/res/values/strings_preferences.xml index 02bc3890ac52480de5a5a9fcb21741ca8f43bd88..7d66a3c236e62f56aa66b1fda696a418321db2ea 100644 --- a/ring-android/app/src/main/res/values/strings_preferences.xml +++ b/ring-android/app/src/main/res/values/strings_preferences.xml @@ -57,4 +57,5 @@ <string name="plugin_recent_version_exception">You have a more recent version of: <b>%1$s</b></string> <string name="plugin_same_version_exception">You have the same version of <b>%1$s</b> installed</string> <string name="plugin_force_install">force \n install</string> + <string name="plugin_path_image_add">Add a new image</string> </resources>