mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Removed UI designer
This commit is contained in:
Generated
+1
@@ -42,6 +42,7 @@
|
||||
<module name="AndroidIDE.gradle-tooling.plugin" target="1.8" />
|
||||
<module name="AndroidIDE.javac" target="1.8" />
|
||||
<module name="AndroidIDE.treesitter.test" target="1.7" />
|
||||
<module name="AndroidIDE.xml-inflater" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
@@ -154,7 +154,6 @@ dependencies {
|
||||
implementation project (':preferences')
|
||||
implementation project (':resources')
|
||||
implementation project (':treeview')
|
||||
implementation project (':xml-inflater')
|
||||
|
||||
implementation fileTree (dir: 'libs', include: ['*.jar'])
|
||||
|
||||
|
||||
@@ -44,10 +44,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".DesignerActivity"
|
||||
android:configChanges="orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".EditorActivity"
|
||||
android:launchMode="singleTop"
|
||||
|
||||
@@ -1,531 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.transition.Fade;
|
||||
import androidx.transition.Slide;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
|
||||
import com.blankj.utilcode.util.DeviceUtils;
|
||||
import com.itsaky.androidide.adapters.WidgetGroupItemAdapter;
|
||||
import com.itsaky.androidide.adapters.WidgetItemAdapter;
|
||||
import com.itsaky.androidide.app.IDEActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityDesignerBinding;
|
||||
import com.itsaky.androidide.fragments.sheets.AttrEditorSheet;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.models.UIWidget;
|
||||
import com.itsaky.androidide.models.UIWidgetGroup;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.ui.WidgetDragData;
|
||||
import com.itsaky.androidide.ui.WidgetDragListener;
|
||||
import com.itsaky.androidide.ui.WidgetDragShadowBuilder;
|
||||
import com.itsaky.androidide.ui.WidgetTouchListener;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IInflateListener;
|
||||
import com.itsaky.inflater.ILayoutInflater;
|
||||
import com.itsaky.inflater.IView;
|
||||
import com.itsaky.inflater.IViewGroup;
|
||||
import com.itsaky.inflater.LayoutInflaterConfiguration;
|
||||
import com.itsaky.inflater.impl.BaseView;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DesignerActivity extends IDEActivity
|
||||
implements WidgetItemAdapter.OnDragStartListener,
|
||||
AttrEditorSheet.OnViewDeletionFailedListener,
|
||||
ILayoutInflater.ContextProvider {
|
||||
|
||||
public static final String KEY_LAYOUT_PATH = "designer_layoutPath";
|
||||
public static final String KEY_RES_DIRS = "designer_resDirs";
|
||||
public static final String KEY_GENERATED_CODE = "designer_xmlCode";
|
||||
public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET";
|
||||
public static final String DRAGGING_WIDGET_MIME = "application/ide_widget";
|
||||
private static final ILogger LOG = ILogger.newInstance("DesignerActivity");
|
||||
public final String EDITOR_SHEET_DIALOG_TAG = "attribute_editor_dialog";
|
||||
private final boolean isTablet = DeviceUtils.isTablet();
|
||||
private final List<UIWidgetGroup> widgetGroups = new ArrayList<>();
|
||||
private ActivityDesignerBinding mBinding;
|
||||
private UIWidgetGroup checkedWidgetCategory;
|
||||
private ILayoutInflater layoutInflater;
|
||||
private IViewGroup inflatedRoot;
|
||||
private File layout;
|
||||
private boolean inflationFailed = false;
|
||||
private AttrEditorSheet mEditorSheet;
|
||||
// This will make sure to apply listeners, background and data to view that are inflated from
|
||||
// XML.
|
||||
private final IInflateListener mInflateListener =
|
||||
new IInflateListener() {
|
||||
|
||||
@Override
|
||||
public void onBeginInflate() {}
|
||||
|
||||
@Override
|
||||
public void onInflateView(IView view, IViewGroup parent) {
|
||||
if (view instanceof IViewGroup) {
|
||||
View v = view.asView();
|
||||
v.setBackgroundResource(R.drawable.bg_design_container_background);
|
||||
v.setOnDragListener(getOnDragListener((IViewGroup) view));
|
||||
}
|
||||
|
||||
setupInflatedView(view);
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyAttribute(IAttribute attr, IView view) {}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate(IView rootView) {
|
||||
if (rootView instanceof IViewGroup) {
|
||||
inflatedRoot = (IViewGroup) rootView;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
||||
if (inflationFailed) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
// When the user presses the back button, set the activity result and finish this activity
|
||||
|
||||
final var progress = new ProgressSheet();
|
||||
progress.setSubMessageEnabled(true);
|
||||
progress.setShowTitle(false);
|
||||
progress.setMessage(getString(R.string.title_generating_xml));
|
||||
progress.setSubMessage(getString(R.string.please_wait));
|
||||
progress.show(getSupportFragmentManager(), "generate_code_progress");
|
||||
|
||||
final var future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode()));
|
||||
future.whenComplete(
|
||||
(s, throwable) -> {
|
||||
progress.dismiss();
|
||||
|
||||
if (throwable != null) {
|
||||
notifyXmlGenerationFailed(throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
finishWithResult(future.get());
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
notifyXmlGenerationFailed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyXmlGenerationFailed(Throwable error) {
|
||||
final var errorMessage = getString(R.string.msg_generate_xml_failed);
|
||||
LOG.error(errorMessage, error);
|
||||
|
||||
DialogUtils.newYesNoDialog(
|
||||
this,
|
||||
getString(R.string.title_code_generation_failed),
|
||||
getString(R.string.msg_code_generation_failed),
|
||||
(dialog1, which) -> finishWithError(),
|
||||
(dialog1, which) -> dialog1.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void finishWithError() {
|
||||
final var intent = new Intent();
|
||||
setResult(-123, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void finishWithResult(String result) {
|
||||
final var intent = new Intent();
|
||||
intent.putExtra(KEY_GENERATED_CODE, result);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStarted(View view) {
|
||||
mBinding.getRoot().closeDrawer(GravityCompat.START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeletionFailed(@NonNull IView view) {
|
||||
final var v = view.asView();
|
||||
if (mBinding.layoutContainer.indexOfChild(v) >= 0) {
|
||||
mBinding.layoutContainer.removeView(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setSupportActionBar(mBinding.toolbar);
|
||||
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
final var toggle =
|
||||
new ActionBarDrawerToggle(
|
||||
this, mBinding.getRoot(), mBinding.toolbar, R.string.app_name, R.string.app_name) {
|
||||
|
||||
@Override
|
||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||
super.onDrawerSlide(drawerView, slideOffset);
|
||||
if (isTablet) {
|
||||
mBinding.mainContainer.setTranslationX(drawerView.getWidth() * slideOffset);
|
||||
}
|
||||
}
|
||||
};
|
||||
mBinding.getRoot().addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
final var extras = getIntent().getExtras();
|
||||
final var path = extras.getString(KEY_LAYOUT_PATH, null);
|
||||
final var name = path.substring(path.lastIndexOf(File.separator) + 1);
|
||||
final var resDirs = extras.getStringArrayList(KEY_RES_DIRS);
|
||||
getSupportActionBar().setTitle(name);
|
||||
|
||||
try {
|
||||
this.layout = new File(path);
|
||||
LOG.debug("Inflating layout file:", this.layout);
|
||||
LOG.debug("Resource directories:", TextUtils.join(",\n", resDirs));
|
||||
|
||||
//noinspection deprecation
|
||||
final var pd = ProgressDialog.show(this, null, getString(R.string.please_wait), true, false);
|
||||
final var dirs = resDirs.stream().map(File::new).collect(Collectors.toSet());
|
||||
final var future = getApp().createInflaterConfig(this, dirs);
|
||||
|
||||
TaskExecutor.executeAsyncProvideError(
|
||||
future::get,
|
||||
(config, throwable) -> {
|
||||
pd.dismiss();
|
||||
|
||||
if (config == null || throwable != null) {
|
||||
LOG.error("Unable to create inflater configuration", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
inflatePath(path, config);
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
onLayoutInflationFailed(th);
|
||||
}
|
||||
|
||||
setupWidgets();
|
||||
|
||||
// Always open the drawer if this device is a tablet.
|
||||
if (isTablet) {
|
||||
mBinding.getRoot().setScrimColor(Color.TRANSPARENT);
|
||||
mBinding.getRoot().openDrawer(GravityCompat.START);
|
||||
mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater());
|
||||
return mBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LOG.logThis();
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
}
|
||||
|
||||
this.mEditorSheet = null;
|
||||
|
||||
// Release the reference to inflate listener and context from the layout inflater
|
||||
// Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment
|
||||
// manager
|
||||
try {
|
||||
if (layoutInflater == null) {
|
||||
LOG.debug("Skipping layout inflater context reference release. Inflater is null");
|
||||
return;
|
||||
}
|
||||
|
||||
this.layoutInflater.unregisterListener(mInflateListener);
|
||||
this.layoutInflater.resetContextProvider(null);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to release activity references from ILayoutInflater", e);
|
||||
}
|
||||
mBinding = null;
|
||||
}
|
||||
|
||||
private boolean isAttrEditorShowing() {
|
||||
return getAttrEditorSheet().getDialog() != null && getAttrEditorSheet().getDialog().isShowing();
|
||||
}
|
||||
|
||||
private AttrEditorSheet getAttrEditorSheet() {
|
||||
return this.mEditorSheet == null
|
||||
? mEditorSheet = new AttrEditorSheet().setLayout(this.layout)
|
||||
: mEditorSheet;
|
||||
}
|
||||
|
||||
protected void onLayoutInflationFailed(Throwable th) {
|
||||
mBinding.layoutContainer.removeAllViews();
|
||||
mBinding.layoutContainer.addView(createErrorText(th));
|
||||
LOG.error(getString(R.string.err_cannot_inflate_layout), th);
|
||||
inflationFailed = true;
|
||||
}
|
||||
|
||||
private void inflatePath(String path, LayoutInflaterConfiguration config) {
|
||||
try {
|
||||
this.layoutInflater = ILayoutInflater.newInstance(config);
|
||||
this.layoutInflater.resetContextProvider(this);
|
||||
this.layoutInflater.registerInflateListener(this.mInflateListener);
|
||||
final IView view = this.layoutInflater.inflatePath(path, mBinding.layoutContainer);
|
||||
|
||||
if (this.inflatedRoot != null) {
|
||||
this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot));
|
||||
}
|
||||
|
||||
mBinding.layoutContainer.addView(view.asView());
|
||||
mBinding.layoutContainer.setOnClickListener(
|
||||
v -> mBinding.getRoot().openDrawer(GravityCompat.START));
|
||||
|
||||
tryDismiss();
|
||||
} catch (Throwable error) {
|
||||
onLayoutInflationFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryDismiss() {
|
||||
final var manager = getSupportFragmentManager();
|
||||
final var frag = manager.findFragmentByTag(EDITOR_SHEET_DIALOG_TAG);
|
||||
if (frag != null) {
|
||||
final var transaction = manager.beginTransaction();
|
||||
transaction.remove(frag);
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupInflatedView(@NonNull IView view) {
|
||||
final var listener =
|
||||
new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick);
|
||||
view.asView().setOnTouchListener(listener);
|
||||
|
||||
setDragDataToInflatedView(view);
|
||||
}
|
||||
|
||||
private boolean onLayoutViewLongClick(@NonNull IView view) {
|
||||
final var shadow = new WidgetDragShadowBuilder(view.asView());
|
||||
final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
|
||||
final var clip =
|
||||
new ClipData(
|
||||
DesignerActivity.DRAGGING_WIDGET_TAG,
|
||||
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
|
||||
item);
|
||||
final var data = view.getExtraData();
|
||||
|
||||
view.asView().startDragAndDrop(clip, shadow, data, 0);
|
||||
view.removeFromParent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onLayoutViewClick(@NonNull IView view) {
|
||||
if (isAttrEditorShowing()) {
|
||||
getAttrEditorSheet().dismiss();
|
||||
}
|
||||
|
||||
getAttrEditorSheet().setSelectedView(view);
|
||||
|
||||
if (!getSupportFragmentManager().isDestroyed()) {
|
||||
getAttrEditorSheet().show(getSupportFragmentManager(), EDITOR_SHEET_DIALOG_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupWidgets() {
|
||||
final var android = new UIWidgetGroup(getString(R.string.widget_group_android));
|
||||
android.addChild(
|
||||
new UIWidget(getString(R.string.widget_button), R.drawable.ic_widget_button, Button.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checkbox), R.drawable.ic_widget_checkbox, CheckBox.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_checked_textview),
|
||||
R.drawable.ic_widget_checked_textview,
|
||||
CheckedTextView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_edittext), R.drawable.ic_widget_edittext, EditText.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_button),
|
||||
R.drawable.ic_widget_image,
|
||||
ImageButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_image_view), R.drawable.ic_widget_image, ImageView.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_progressbar),
|
||||
R.drawable.ic_widget_progress,
|
||||
ProgressBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_radio_button),
|
||||
R.drawable.ic_widget_radio_button,
|
||||
RadioButton.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_seekbar), R.drawable.ic_widget_seekbar, SeekBar.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_spinner), R.drawable.ic_widget_spinner, Spinner.class));
|
||||
android.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.widget_textview), R.drawable.ic_widget_textview, TextView.class));
|
||||
|
||||
// IMPORTANT: Do not select any other groups here
|
||||
android.setSelected(true);
|
||||
this.checkedWidgetCategory = android;
|
||||
widgetGroups.add(android);
|
||||
|
||||
final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_linear), R.drawable.ic_widget_linear, LinearLayout.class));
|
||||
layouts.addChild(
|
||||
new UIWidget(
|
||||
getString(R.string.layout_relative),
|
||||
R.drawable.ic_widget_relative,
|
||||
RelativeLayout.class));
|
||||
|
||||
widgetGroups.add(layouts);
|
||||
|
||||
final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup);
|
||||
mBinding.groupItems.setAdapter(adapter);
|
||||
|
||||
showWidgetGroup(android);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void showWidgetGroup(@NonNull UIWidgetGroup group) {
|
||||
final var children = group.getChildren();
|
||||
this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this));
|
||||
this.checkedWidgetCategory.setSelected(false);
|
||||
this.checkedWidgetCategory = group;
|
||||
this.checkedWidgetCategory.setSelected(true);
|
||||
|
||||
TransitionManager.beginDelayedTransition(
|
||||
mBinding.navigation,
|
||||
new TransitionSet().addTransition(new Slide(Gravity.END)).addTransition(new Fade()));
|
||||
|
||||
this.mBinding.groupItems.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private TextView createErrorText(Throwable th) {
|
||||
final TextView error = new TextView(this);
|
||||
error.setText(createErrorMessage(th));
|
||||
error.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
|
||||
error.setTextColor(ContextCompat.getColor(this, android.R.color.black));
|
||||
error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
error.setGravity(Gravity.CENTER);
|
||||
return error;
|
||||
}
|
||||
|
||||
private CharSequence createErrorMessage(final Throwable th) {
|
||||
final var sb = new StringBuilder();
|
||||
|
||||
if (th == null) {
|
||||
sb.append("Unknown error");
|
||||
return sb;
|
||||
}
|
||||
|
||||
var err = th;
|
||||
var first = true;
|
||||
while (err != null) {
|
||||
if (!first) {
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append(err.getMessage());
|
||||
err = err.getCause();
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private View.OnDragListener getOnDragListener(IViewGroup group) {
|
||||
// WidgetDragListener cannot be reused
|
||||
// This is because they keep a reference to the view group in which dragged view will be
|
||||
// added
|
||||
|
||||
return new WidgetDragListener(this, group, this::setupInflatedView);
|
||||
}
|
||||
|
||||
private void setDragDataToInflatedView(@NonNull IView view) {
|
||||
view.setExtraData(new WidgetDragData(true, view, null));
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@ import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.GravityInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -128,19 +125,14 @@ import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry;
|
||||
import com.itsaky.androidide.xml.versions.ApiVersionsRegistry;
|
||||
import com.itsaky.androidide.xml.widgets.WidgetTableRegistry;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -167,7 +159,7 @@ public class EditorActivity extends IDEActivity
|
||||
private final EditorEventListener mBuildEventListener = new EditorEventListener();
|
||||
private final EditorActivityLifecyclerObserver mLifecycleObserver =
|
||||
new EditorActivityLifecyclerObserver();
|
||||
|
||||
|
||||
private ActivityEditorBinding mBinding;
|
||||
private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding;
|
||||
private final LogReceiver mLogReceiver = new LogReceiver().setLogListener(this::appendApkLog);
|
||||
@@ -176,7 +168,6 @@ public class EditorActivity extends IDEActivity
|
||||
private TextSheetFragment mDaemonStatusFragment;
|
||||
private ProgressSheet mSearchingProgress;
|
||||
private AlertDialog mFindInProjectDialog;
|
||||
private ActivityResultLauncher<Intent> mUIDesignerLauncher;
|
||||
private BottomSheetBehavior<? extends View> mEditorBottomSheet;
|
||||
private EditorViewModel mViewModel;
|
||||
private final ServiceConnection mGradleServiceConnection =
|
||||
@@ -404,7 +395,7 @@ public class EditorActivity extends IDEActivity
|
||||
if (mBinding == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
|
||||
final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt(i);
|
||||
if (file.equals(editor.getFile())) {
|
||||
@@ -429,7 +420,7 @@ public class EditorActivity extends IDEActivity
|
||||
IntentUtils.openImage(this, file);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (mBinding == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -713,27 +704,7 @@ public class EditorActivity extends IDEActivity
|
||||
}
|
||||
}
|
||||
|
||||
public void previewLayout() {
|
||||
try {
|
||||
|
||||
if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) {
|
||||
LOG.error("No file is opened. Cannot preview layout.");
|
||||
return;
|
||||
}
|
||||
|
||||
saveAll(false);
|
||||
|
||||
final Intent intent = new Intent(this, DesignerActivity.class);
|
||||
intent.putExtra(
|
||||
DesignerActivity.KEY_LAYOUT_PATH, getCurrentEditor().getFile().getAbsolutePath());
|
||||
intent.putStringArrayListExtra(DesignerActivity.KEY_RES_DIRS, getResourceDirPaths());
|
||||
LOG.info("Launching UI Designer...");
|
||||
mUIDesignerLauncher.launch(intent);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(string.err_cannot_preview_layout), th);
|
||||
toast(string.msg_cannot_preview_layout, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
public void previewLayout() {}
|
||||
|
||||
public boolean saveAll(boolean notify) {
|
||||
return saveAll(notify, false);
|
||||
@@ -931,10 +902,6 @@ public class EditorActivity extends IDEActivity
|
||||
setupContainers();
|
||||
setupDiagnosticInfo();
|
||||
|
||||
mUIDesignerLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(), this::onGetUIDesignerResult);
|
||||
|
||||
EditorActivityActions.register(this);
|
||||
}
|
||||
|
||||
@@ -1081,25 +1048,6 @@ public class EditorActivity extends IDEActivity
|
||||
} catch (Throwable th) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
final var resDirs = getResourceDirs();
|
||||
resDirs.removeIf(Objects::isNull);
|
||||
ValuesTableFactory.setupWithResDirectories(resDirs.toArray(new File[0]));
|
||||
});
|
||||
}
|
||||
|
||||
private Set<File> getResourceDirs() {
|
||||
return ProjectManager.INSTANCE.getApplicationResDirectories();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ArrayList<String> getResourceDirPaths() {
|
||||
return getResourceDirs().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
private void setupDrawerToggle() {
|
||||
@@ -1297,31 +1245,6 @@ public class EditorActivity extends IDEActivity
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private void onGetUIDesignerResult(@NonNull ActivityResult result) {
|
||||
if (mBinding == null) {
|
||||
return;
|
||||
}
|
||||
final var index = mBinding.editorContainer.getDisplayedChild();
|
||||
final var editor = getEditorAtIndex(index);
|
||||
if (editor != null && result.getResultCode() == RESULT_OK) {
|
||||
final var data = result.getData();
|
||||
if (data != null && data.hasExtra(DesignerActivity.KEY_GENERATED_CODE)) {
|
||||
final var code = data.getStringExtra(DesignerActivity.KEY_GENERATED_CODE);
|
||||
editor.getEditor().replaceContent(code);
|
||||
editor.markModified();
|
||||
saveAll();
|
||||
} else {
|
||||
final var msg = getString(string.msg_invalid_designer_result);
|
||||
toast(msg, Toaster.Type.ERROR);
|
||||
LOG.error(msg, "Data returned by UI Designer is null or is invalid.");
|
||||
}
|
||||
} else {
|
||||
LOG.error(
|
||||
"UI Designer returned an invalid result code.", "Result code: " + result.getResultCode());
|
||||
}
|
||||
}
|
||||
|
||||
private void openTerminal() {
|
||||
final Intent intent = new Intent(this, TerminalActivity.class);
|
||||
intent.putExtra(
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An {@link com.itsaky.androidide.adapters.IconTextAdapter} for {@link
|
||||
* com.itsaky.attrinfo.models.Attr}.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrListAdapter extends IconTextAdapter<Attr> {
|
||||
|
||||
private final Consumer<Attr> clickConsumer;
|
||||
private final List<Attr> attrs;
|
||||
|
||||
public AttrListAdapter(Consumer<Attr> clickConsumer, List<Attr> attrs) {
|
||||
this.clickConsumer = clickConsumer;
|
||||
if (attrs == null) {
|
||||
attrs = new ArrayList<>();
|
||||
}
|
||||
|
||||
this.attrs = attrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return attrs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
if (clickConsumer != null) {
|
||||
holder.binding.getRoot().setOnClickListener(v -> clickConsumer.accept(getItemAt(position)));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Attr getItemAt(int index) {
|
||||
return attrs.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconResource(int index) {
|
||||
return R.drawable.ic_xml_attribute;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getItemText(int index) {
|
||||
return getItemAt(index).name;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Adapter for showing the autocomplete items while editing attribute values in UI Designer.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrValueCompletionAdapter extends BaseAdapter implements Filterable {
|
||||
|
||||
private final List<String> items;
|
||||
private final List<String> filtered;
|
||||
private final Context context;
|
||||
private final Filter mFilter =
|
||||
new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
final var result = new FilterResults();
|
||||
if (TextUtils.isEmpty(constraint)) {
|
||||
final var list = new ArrayList<>(items);
|
||||
result.values = list;
|
||||
result.count = list.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
final var match = constraint.toString().trim().toLowerCase(Locale.ROOT);
|
||||
final var list = new ArrayList<String>();
|
||||
for (String item : items) {
|
||||
if (item.contains(match)) {
|
||||
list.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
result.values = list;
|
||||
result.count = list.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, @NonNull FilterResults results) {
|
||||
//noinspection unchecked
|
||||
List<String> items = (List<String>) results.values;
|
||||
Objects.requireNonNull(items);
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(items);
|
||||
|
||||
if (items.isEmpty()) {
|
||||
notifyDataSetInvalidated();
|
||||
} else {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AttrValueCompletionAdapter(Context context, List<String> items) {
|
||||
this.context = context;
|
||||
this.items = items;
|
||||
this.filtered = new ArrayList<>(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return this.filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return filtered.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView =
|
||||
((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
|
||||
.inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||
}
|
||||
|
||||
((TextView) convertView).setText(getItem(position).toString());
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return mFilter;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Adapter for value format tabs of attribute editor in UI Designer.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class AttrValueFormatTabAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("AttrValueEditorTabAdapter");
|
||||
private final List<Fragment> mFragments = new ArrayList<>();
|
||||
private final List<String> mTitles = new ArrayList<>();
|
||||
private final XMLAttribute attribute;
|
||||
|
||||
public AttrValueFormatTabAdapter(@NonNull FragmentManager manager, XMLAttribute attribute) {
|
||||
super(manager);
|
||||
Objects.requireNonNull(attribute);
|
||||
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
public void addFragment(final Class<? extends BaseValueEditorFragment> fragment, String title) {
|
||||
Objects.requireNonNull(fragment);
|
||||
Objects.requireNonNull(title);
|
||||
if (TextUtils.isEmpty(title.trim())) {
|
||||
throw new IllegalArgumentException("Invalid tab title");
|
||||
}
|
||||
|
||||
mTitles.add(capitalize(title));
|
||||
mFragments.add(createFragment(fragment, title));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String capitalize(String title) {
|
||||
title = title.trim();
|
||||
|
||||
final var sb = new StringBuilder();
|
||||
for (int i = 0; i < title.length(); i++) {
|
||||
var ch = title.charAt(i);
|
||||
if (i == 0) {
|
||||
ch = Character.toUpperCase(ch);
|
||||
} else {
|
||||
ch = Character.toLowerCase(ch);
|
||||
}
|
||||
|
||||
sb.append(ch);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Fragment createFragment(
|
||||
Class<? extends BaseValueEditorFragment> clazz, @NonNull final String name) {
|
||||
try {
|
||||
final var frag = clazz.newInstance();
|
||||
final var args = new Bundle();
|
||||
args.putParcelable(BaseValueEditorFragment.KEY_ATTR, attribute);
|
||||
args.putString(BaseValueEditorFragment.KEY_NAME, name);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to create value editor fragment", th);
|
||||
throw new RuntimeException(th);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
mFragments.clear();
|
||||
mTitles.clear();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return mFragments.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFragments.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return mTitles.get(position);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.models.IconTextListItem;
|
||||
import com.itsaky.androidide.models.UIWidgetGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WidgetGroupItemAdapter extends SimpleIconTextAdapter
|
||||
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
|
||||
|
||||
private final OnGroupClickListener clickListener;
|
||||
|
||||
public WidgetGroupItemAdapter(
|
||||
@NonNull List<UIWidgetGroup> groups, @Nullable OnGroupClickListener clickListener) {
|
||||
super(groups);
|
||||
this.clickListener = clickListener;
|
||||
|
||||
super.setOnBindListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBind(IconTextListItem item, VH holder, int position) {
|
||||
// Ignored
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(IconTextListItem item, @NonNull VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
final var group = (UIWidgetGroup) item;
|
||||
|
||||
if (group.isSelected()) {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackground(
|
||||
ContextCompat.getDrawable(
|
||||
binding.getRoot().getContext(), R.drawable.bg_widget_group_selected));
|
||||
} else {
|
||||
binding.getRoot().setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
if (this.clickListener != null) {
|
||||
binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group));
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnGroupClickListener {
|
||||
void onGroupClick(UIWidgetGroup group);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.DesignerActivity;
|
||||
import com.itsaky.androidide.models.IconTextListItem;
|
||||
import com.itsaky.androidide.models.UIWidget;
|
||||
import com.itsaky.androidide.ui.WidgetDragData;
|
||||
import com.itsaky.androidide.ui.WidgetDragShadowBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WidgetItemAdapter extends SimpleIconTextAdapter
|
||||
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
|
||||
|
||||
private final OnDragStartListener dragStartListener;
|
||||
|
||||
public WidgetItemAdapter(List<UIWidget> widgets, OnDragStartListener dragStartListener) {
|
||||
super(widgets);
|
||||
this.dragStartListener = dragStartListener;
|
||||
|
||||
setOnBindListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) {
|
||||
return false; // Returning true will not set the text and icon
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(IconTextListItem item, VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
final var root = binding.getRoot();
|
||||
final var widget = (UIWidget) getItemAt(position);
|
||||
|
||||
root.setOnLongClickListener(
|
||||
v -> {
|
||||
final var dragData = new WidgetDragData(false, null, widget);
|
||||
final var shadow = new WidgetDragShadowBuilder(binding.icon);
|
||||
final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
|
||||
final var data =
|
||||
new ClipData(
|
||||
DesignerActivity.DRAGGING_WIDGET_TAG,
|
||||
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
|
||||
dataItem);
|
||||
binding.icon.startDragAndDrop(data, shadow, dragData, 0);
|
||||
|
||||
if (this.dragStartListener != null) {
|
||||
this.dragStartListener.onDragStarted(v);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnDragStartListener {
|
||||
void onDragStarted(View view);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrEditorSheetItemBinding;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class XMLAttributeListAdapter extends RecyclerView.Adapter<XMLAttributeListAdapter.VH> {
|
||||
|
||||
private final List<XMLAttribute> attributes;
|
||||
private final OnClickListener clickListener;
|
||||
|
||||
public XMLAttributeListAdapter(List<XMLAttribute> attributes, OnClickListener clickListener) {
|
||||
this.attributes = attributes;
|
||||
this.clickListener = clickListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new VH(
|
||||
LayoutAttrEditorSheetItemBinding.inflate(
|
||||
LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
final var attr = attributes.get(position);
|
||||
final var sb = new SpannableStringBuilder();
|
||||
sb.append(attr.getAttributeName());
|
||||
if (attr.isApplied()) {
|
||||
sb.append(" = ");
|
||||
sb.append(attr.getValue(), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
final var length = attr.getValue().length();
|
||||
sb.setSpan(
|
||||
new ForegroundColorSpan(
|
||||
ContextCompat.getColor(binding.text.getContext(), R.color.primaryTextColor)),
|
||||
sb.length() - length,
|
||||
sb.length() - 1,
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
binding.text.setText(sb);
|
||||
binding
|
||||
.getRoot()
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
if (this.clickListener != null) {
|
||||
this.clickListener.onClick(binding, attr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
static class VH extends RecyclerView.ViewHolder {
|
||||
LayoutAttrEditorSheetItemBinding binding;
|
||||
|
||||
VH(@NonNull LayoutAttrEditorSheetItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute);
|
||||
}
|
||||
}
|
||||
@@ -23,36 +23,20 @@ import android.net.Uri;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.itsaky.androidide.BuildConfig;
|
||||
import com.itsaky.androidide.CrashHandlerActivity;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.events.AppEventsIndex;
|
||||
import com.itsaky.androidide.events.LspApiEventsIndex;
|
||||
import com.itsaky.androidide.events.LspJavaEventsIndex;
|
||||
import com.itsaky.androidide.events.ProjectsApiEventsIndex;
|
||||
import com.itsaky.androidide.preferences.internal.XmlPreferencesKt;
|
||||
import com.itsaky.androidide.projects.ProjectResourceTable;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.inflater.ILayoutInflater;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.LayoutInflaterConfiguration;
|
||||
import com.itsaky.sdk.SDKInfo;
|
||||
import com.itsaky.toaster.ToastUtilsKt;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public class IDEApplication extends BaseApplication {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("IDEApplication");
|
||||
private static IDEApplication instance;
|
||||
private static SDKInfo sdkInfo;
|
||||
private IResourceTable mResTable;
|
||||
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
|
||||
|
||||
public static IDEApplication getInstance() {
|
||||
@@ -72,8 +56,6 @@ public class IDEApplication extends BaseApplication {
|
||||
.addIndex(new LspApiEventsIndex())
|
||||
.addIndex(new LspJavaEventsIndex())
|
||||
.installDefaultEventBus();
|
||||
|
||||
getApiInformation();
|
||||
}
|
||||
|
||||
private void handleCrash(Thread thread, Throwable th) {
|
||||
@@ -95,54 +77,6 @@ public class IDEApplication extends BaseApplication {
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads API version information from api-versions.xml */
|
||||
public CompletableFuture<SDKInfo> getApiInformation() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
if (sdkInfo == null) {
|
||||
try {
|
||||
sdkInfo = new SDKInfo(IDEApplication.this);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_init_sdkinfo), th);
|
||||
}
|
||||
}
|
||||
|
||||
return sdkInfo;
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<LayoutInflaterConfiguration> createInflaterConfig(
|
||||
ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
|
||||
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
final var sdkInfo = getApiInformation().get();
|
||||
return new LayoutInflaterConfiguration.Builder()
|
||||
.setAttrInfo(sdkInfo.getAttrInfo())
|
||||
.setWidgetInfo(sdkInfo.getWidgetInfo())
|
||||
.setResourceFinder(getResourceTable())
|
||||
.setResourceDirectories(resDirs)
|
||||
.setContextProvider(contextProvider)
|
||||
.create();
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public IResourceTable getResourceTable() {
|
||||
return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable;
|
||||
}
|
||||
|
||||
public AttrInfo attrInfo() {
|
||||
return sdkInfo.getAttrInfo();
|
||||
}
|
||||
|
||||
public WidgetInfo widgetInfo() {
|
||||
return sdkInfo.getWidgetInfo();
|
||||
}
|
||||
|
||||
public void showChangelog() {
|
||||
final var intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
|
||||
protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull(referenceInput);
|
||||
|
||||
referenceInput.setText(attribute.getValue());
|
||||
|
||||
if (resInputWatcher != null) {
|
||||
referenceInput.removeTextChangedListener(resInputWatcher);
|
||||
}
|
||||
|
||||
resInputWatcher =
|
||||
new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
final var text = s.toString().trim();
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyValueChanged(text);
|
||||
}
|
||||
};
|
||||
|
||||
referenceInput.addTextChangedListener(resInputWatcher);
|
||||
|
||||
CompletableFuture.supplyAsync(this::computeReferenceItems)
|
||||
.whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged(newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult(
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
LOG.error("No completion items found");
|
||||
LOG.error("Error was:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Found", list.size(), "resource items for completion");
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
final var adapter = new AttrValueCompletionAdapter(requireContext(), list);
|
||||
referenceInput.setThreshold(1);
|
||||
referenceInput.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
protected List<String> computeReferenceItems() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base class for value editor fragment.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class BaseValueEditorFragment extends Fragment {
|
||||
|
||||
public static final String KEY_ATTR = "editor_attr";
|
||||
public static final String KEY_NAME = "editor_name";
|
||||
protected OnValueChangeListener mValueChangeListener;
|
||||
protected XMLAttribute attribute;
|
||||
protected String name;
|
||||
|
||||
protected NestedScrollView scrollView;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mValueChangeListener = (OnValueChangeListener) getParentFragment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
if (scrollView != null) {
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
scrollView = new NestedScrollView(inflater.getContext());
|
||||
scrollView.setFillViewport(true);
|
||||
scrollView.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
|
||||
|
||||
final var child = createView(inflater, scrollView, savedInstanceState);
|
||||
if (child != null) {
|
||||
scrollView.addView(child);
|
||||
}
|
||||
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState);
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final var args = requireArguments();
|
||||
this.attribute = args.getParcelable(KEY_ATTR);
|
||||
this.name = args.getString(KEY_NAME);
|
||||
}
|
||||
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
Objects.requireNonNull(newValue);
|
||||
|
||||
if (mValueChangeListener != null) {
|
||||
mValueChangeListener.onValueChanged(this.attribute, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for listening for value change event. */
|
||||
public interface OnValueChangeListener {
|
||||
/**
|
||||
* Called when the value was changed.
|
||||
*
|
||||
* @param attribute The attribute whose value was changed.
|
||||
* @param newValue The new value for the editor.
|
||||
*/
|
||||
void onValueChanged(IAttribute attribute, String newValue);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.databinding.LayoutBooleanAttrEditorBinding;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class BooleanEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutBooleanAttrEditorBinding binding;
|
||||
|
||||
@Override
|
||||
protected @Nullable View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.binding = LayoutBooleanAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.binding.choiceGroup.setOnCheckedChangeListener(
|
||||
(group, checkedId) -> {
|
||||
final var checked = (RadioButton) group.findViewById(checkedId);
|
||||
notifyValueChanged(checked.getText().toString().trim());
|
||||
});
|
||||
|
||||
if (!TextUtils.isEmpty(attribute.getValue())) {
|
||||
switch (attribute.getValue()) {
|
||||
case "true":
|
||||
this.binding.boolTrue.setChecked(true);
|
||||
break;
|
||||
case "false":
|
||||
this.binding.boolFalse.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("bool");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@bool/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listBools().stream()
|
||||
.map("@android:bool/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.app.IDEApplication;
|
||||
import com.itsaky.androidide.colorpicker.ColorPickerView;
|
||||
import com.itsaky.androidide.databinding.LayoutColorAttrEditorBinding;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.inflater.util.CommonParseUtils;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ColorEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutColorAttrEditorBinding binding;
|
||||
private Dialog colorPickerDialog;
|
||||
|
||||
@Override
|
||||
protected @Nullable View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.binding = LayoutColorAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.binding.pickColor.setOnClickListener(v -> getColorPickerDialog().show());
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.colorResInput.getEditText());
|
||||
|
||||
try {
|
||||
CommonParseUtils parser =
|
||||
new CommonParseUtils(
|
||||
IDEApplication.getInstance().getResourceTable(),
|
||||
IDEApplication.getInstance().getApplicationContext().getResources().getDisplayMetrics());
|
||||
final var col = parser.parseColor(attribute.getValue(), requireContext());
|
||||
this.binding.colorPreview.setCardBackgroundColor(col);
|
||||
} catch (Throwable th) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private Dialog getColorPickerDialog() {
|
||||
|
||||
if (colorPickerDialog == null) {
|
||||
final var builder = DialogUtils.newMaterialDialogBuilder(requireContext());
|
||||
ColorPickerView colorPickerView = new ColorPickerView(requireContext());
|
||||
|
||||
builder.setView(colorPickerView);
|
||||
|
||||
colorPickerView.setOnPickListener(
|
||||
(color, hexCode) -> {
|
||||
if (colorPickerDialog != null) {
|
||||
colorPickerDialog.dismiss();
|
||||
}
|
||||
|
||||
this.binding.colorPreview.setCardBackgroundColor(color);
|
||||
notifyValueChanged(hexCode);
|
||||
});
|
||||
|
||||
colorPickerDialog = builder.create();
|
||||
}
|
||||
|
||||
return colorPickerDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("color");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@color/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listColors().stream()
|
||||
.map("@android:color/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
final var resTable = IDEApplication.getInstance().getResourceTable();
|
||||
list.addAll(resTable.listResourceNames("color"));
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.databinding.LayoutDimensionAttrEditorBinding;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionEditor extends AbstractReferenceEditor {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("DimensionEditor");
|
||||
public TextWatcherAdapter dimensionInputWatcher;
|
||||
|
||||
private LayoutDimensionAttrEditorBinding binding;
|
||||
private String[] dimensionUnits;
|
||||
|
||||
@Override
|
||||
protected @Nullable View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
final var dimensionInput = Objects.requireNonNull(this.binding.dimensionInput.getEditText());
|
||||
setDimensionValue(dimensionInput);
|
||||
if (dimensionInputWatcher != null) {
|
||||
dimensionInput.removeTextChangedListener(dimensionInputWatcher);
|
||||
}
|
||||
|
||||
dimensionInputWatcher =
|
||||
new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
final var text = s.toString().trim();
|
||||
final var unit = getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()];
|
||||
|
||||
// This will call CommonParseUtils#parseDimension
|
||||
notifyValueChanged(text.concat(unit));
|
||||
}
|
||||
};
|
||||
|
||||
dimensionInput.addTextChangedListener(dimensionInputWatcher);
|
||||
|
||||
final var dimensionResInput =
|
||||
(MaterialAutoCompleteTextView)
|
||||
Objects.requireNonNull(binding.dimensionResInput.getEditText());
|
||||
setupReferenceInput(dimensionResInput);
|
||||
}
|
||||
|
||||
private void setDimensionValue(EditText dimensionInput) {
|
||||
final var val = attribute.getValue();
|
||||
if (TextUtils.isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dimension = val.substring(0, val.length() - 2);
|
||||
if (TextUtils.isDigitsOnly(dimension)) {
|
||||
dimensionInput.setText(dimension);
|
||||
final var arr = requireContext().getResources().getStringArray(R.array.dimension_units);
|
||||
final var index = Arrays.asList(arr).indexOf(val.substring(dimension.length()));
|
||||
if (index >= 0) {
|
||||
this.binding.unitSelector.setSelection(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] getDimensionUnits() {
|
||||
if (dimensionUnits == null) {
|
||||
dimensionUnits = requireContext().getResources().getStringArray(R.array.dimension_units);
|
||||
}
|
||||
|
||||
return dimensionUnits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("dimen");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@dimen/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listDimens().stream()
|
||||
.map("@android:dimen/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class EnumEditor extends FixedValueEditor {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("EnumEditor");
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.chipGroup.setSingleSelection(true);
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
|
||||
final var val = this.attribute.getValue();
|
||||
if (!TextUtils.isEmpty(val)) {
|
||||
for (int i = 0; i < this.chipGroup.getChildCount(); i++) {
|
||||
final var chip = (Chip) this.chipGroup.getChildAt(i);
|
||||
if (val.equals(chip.getText().toString().trim())) {
|
||||
chip.setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) {
|
||||
final var chip = (Chip) group.findViewById(group.getCheckedChipId());
|
||||
if (chip == null) {
|
||||
LOG.error("Unable to update enum value. Checked Chip view is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var val = chip.getText().toString().trim();
|
||||
notifyValueChanged(val);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class FixedValueEditor extends BaseValueEditorFragment {
|
||||
|
||||
protected ChipGroup chipGroup;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.chipGroup = new ChipGroup(inflater.getContext());
|
||||
this.chipGroup.setLayoutParams(
|
||||
new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
this.chipGroup.setSingleSelection(false);
|
||||
|
||||
return this.chipGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
Objects.requireNonNull(this.attribute);
|
||||
Objects.requireNonNull(this.chipGroup);
|
||||
|
||||
this.chipGroup.setSingleSelection(false);
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
|
||||
final var attr = this.attribute.getAttr();
|
||||
for (var value : attr.possibleValues) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.chipGroup.addView(newChip(value, value.equals(this.attribute.getValue())));
|
||||
}
|
||||
|
||||
this.chipGroup.setOnCheckedChangeListener(this::onCheckChanged);
|
||||
}
|
||||
|
||||
protected abstract void onCheckChanged(@NonNull ChipGroup group, int checkedId);
|
||||
|
||||
protected Chip newChip(String title, boolean checked) {
|
||||
final var chip = new Chip(requireContext());
|
||||
chip.setId(View.generateViewId());
|
||||
chip.setText(title);
|
||||
chip.setCheckedIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_ok));
|
||||
chip.setChecked(checked);
|
||||
chip.setChipBackgroundColor(
|
||||
ContextCompat.getColorStateList(requireContext(), R.color.bg_enum_chips));
|
||||
chip.setTextColor(ContextCompat.getColor(requireContext(), R.color.primaryTextColor));
|
||||
chip.setLayoutParams(
|
||||
new ChipGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
chip.setChipStrokeWidth(1f);
|
||||
chip.setChipStrokeColor(
|
||||
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.primaryDarkColor)));
|
||||
|
||||
return chip;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class FlagEditor extends FixedValueEditor {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("FlagEditor");
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.chipGroup.setSingleSelection(false);
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
this.chipGroup.setOnCheckedChangeListener(null); // does not work for multiple selections
|
||||
|
||||
if (!TextUtils.isEmpty(attribute.getValue())) {
|
||||
final var values = Arrays.asList(this.attribute.getValue().split("\\|"));
|
||||
for (int i = 0; i < this.chipGroup.getChildCount(); i++) {
|
||||
final var chip = (Chip) this.chipGroup.getChildAt(i);
|
||||
if (values.contains(chip.getText().toString().trim())) {
|
||||
chip.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) {
|
||||
// Does noting...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Chip newChip(String title, boolean checked) {
|
||||
final var chip = super.newChip(title, checked);
|
||||
chip.setOnCheckedChangeListener(this::onCheckChangedMultiple);
|
||||
return chip;
|
||||
}
|
||||
|
||||
protected void onCheckChangedMultiple(CompoundButton compoundButton, boolean b) {
|
||||
final var items = new ArrayList<String>();
|
||||
for (var id : this.chipGroup.getCheckedChipIds()) {
|
||||
final var chip = (Chip) this.chipGroup.findViewById(id);
|
||||
if (chip == null) {
|
||||
LOG.error("Unable to find chip with checked id:", id);
|
||||
continue;
|
||||
}
|
||||
|
||||
final var val = chip.getText().toString().trim();
|
||||
items.add(val);
|
||||
}
|
||||
|
||||
notifyValueChanged(TextUtils.join("|", items));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class FloatEditor extends UnimplementedEditorFragment {}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class FractionEditor extends UnimplementedEditorFragment {}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class IntegerEditor extends UnimplementedEditorFragment {}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.app.IDEApplication;
|
||||
import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ReferenceEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutReferenceAttrEditorBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var resTable = IDEApplication.getInstance().getResourceTable();
|
||||
final var list = new ArrayList<>(resTable.listResourceNames(null));
|
||||
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var resourceMap = entry.getValue().getResourceMap();
|
||||
for (var resourceEntries : resourceMap.entrySet()) {
|
||||
final var name = resourceEntries.getKey();
|
||||
list.addAll(
|
||||
resourceEntries.getValue().keySet().stream()
|
||||
.map(("@android:" + name + "/")::concat)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(FrameworkValues.listAllResources());
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.databinding.LayoutStringAttrEditorBinding;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class StringEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutStringAttrEditorBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected View createView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
this.binding = LayoutStringAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
final var stringInput = Objects.requireNonNull(this.binding.stringInput.getEditText());
|
||||
stringInput.addTextChangedListener(
|
||||
new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
notifyValueChanged(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
stringInput.setText(this.attribute.getValue());
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.stringResInput.getEditText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("string");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@string/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listStrings().stream()
|
||||
.map("@android:string/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
/** @author Akash Yadav */
|
||||
open class UnimplementedEditorFragment : BaseValueEditorFragment() {
|
||||
override fun createView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.sheets;
|
||||
|
||||
import static com.itsaky.androidide.resources.R.drawable;
|
||||
import static com.itsaky.androidide.resources.R.string;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.adapters.SimpleIconTextAdapter;
|
||||
import com.itsaky.androidide.adapters.XMLAttributeListAdapter;
|
||||
import com.itsaky.androidide.app.IDEApplication;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrEditorSheetBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrEditorSheetItemBinding;
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.models.IconTextListItem;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IView;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.itsaky.toaster.ToastUtilsKt;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem>,
|
||||
Consumer<Attr>,
|
||||
BaseValueEditorFragment.OnValueChangeListener {
|
||||
|
||||
private static final List<IconTextListItem> VIEW_ACTIONS = new ArrayList<>();
|
||||
private static final ILogger LOG = ILogger.newInstance("AttrBottomSheet");
|
||||
private AttributeListSheet mAttrListSheet;
|
||||
private AttrValueEditorSheet mValueEditorSheet;
|
||||
private IView selectedView;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private File layout;
|
||||
|
||||
private LayoutAttrEditorSheetBinding binding;
|
||||
private OnViewDeletionFailedListener mDeletionFailedListener;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding =
|
||||
LayoutAttrEditorSheetBinding.inflate(LayoutInflater.from(getContext()), container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (VIEW_ACTIONS.isEmpty()) {
|
||||
Collections.addAll(
|
||||
VIEW_ACTIONS,
|
||||
IconTextListItem.create(getString(string.msg_viewaction_add_attr), drawable.ic_add),
|
||||
IconTextListItem.create(getString(string.title_viewaction_delete), drawable.ic_delete),
|
||||
IconTextListItem.create(
|
||||
getString(string.msg_viewaction_select_parent), drawable.ic_view_select_parent));
|
||||
}
|
||||
|
||||
setupViewData();
|
||||
}
|
||||
|
||||
private void setupViewData() {
|
||||
binding.actionsList.setAdapter(new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this));
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
binding.widgetName.setText(this.selectedView.getXmlTag());
|
||||
binding.attrList.setAdapter(
|
||||
new XMLAttributeListAdapter(
|
||||
this.selectedView.getAttributes().stream()
|
||||
.map(XMLAttribute::new)
|
||||
.collect(Collectors.toList()),
|
||||
this::onAttrClick));
|
||||
}
|
||||
|
||||
private void onAttrClick(
|
||||
LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
|
||||
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.error("Cannot edit attributes of a null view.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var format = attribute.findFormat();
|
||||
if (format == -1) {
|
||||
ToastUtilsKt.toast(getString(string.msg_no_attr_format), Toaster.Type.ERROR);
|
||||
LOG.error(getString(string.msg_no_attr_format), attribute);
|
||||
return;
|
||||
}
|
||||
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
private void showValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
|
||||
if (mValueEditorSheet == null) {
|
||||
mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute);
|
||||
}
|
||||
|
||||
mValueEditorSheet.setAttribute(attribute);
|
||||
return mValueEditorSheet;
|
||||
}
|
||||
|
||||
private boolean dismissIfSelectedNull() {
|
||||
if (selectedView == null) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mDeletionFailedListener = (OnViewDeletionFailedListener) context;
|
||||
}
|
||||
|
||||
public AttrEditorSheet setLayout(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setSelectedView(IView view) {
|
||||
this.selectedView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(
|
||||
IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
|
||||
final var binding = holder.binding;
|
||||
binding.getRoot().setOnClickListener(v -> onViewActionClick(position));
|
||||
}
|
||||
|
||||
private void onViewActionClick(int position) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (position == 0) { // Add attribute
|
||||
var tag = this.selectedView.getXmlTag();
|
||||
if ("include".equals(tag) || "merge".equals(tag)) {
|
||||
tag = "View";
|
||||
}
|
||||
|
||||
final var attrs = IDEApplication.getInstance().attrInfo();
|
||||
final var style = attrs.getStyle(tag);
|
||||
if (style == null) {
|
||||
LOG.error("Unable to retrieve attributes for tag:", tag);
|
||||
return;
|
||||
}
|
||||
|
||||
final var attributes = new TreeSet<Attr>(Comparator.comparing(attr -> attr.name));
|
||||
attributes.addAll(style.attributes);
|
||||
attributes.addAll(attrs.NO_PARENT.attributes);
|
||||
|
||||
final var widgetInfo = IDEApplication.getInstance().widgetInfo();
|
||||
final var widget = widgetInfo.getWidgetBySimpleName(tag);
|
||||
if (widget != null) {
|
||||
for (var superclass : widget.superclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
break;
|
||||
}
|
||||
|
||||
final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1);
|
||||
final var superStyle = attrs.getStyle(simpleName);
|
||||
if (superStyle != null) {
|
||||
attributes.addAll(superStyle.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var parent = this.selectedView.getParent();
|
||||
if (parent != null) {
|
||||
final var parentTag = parent.getXmlTag();
|
||||
final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag);
|
||||
if (parentWidget != null) {
|
||||
final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout");
|
||||
if (parentLayoutParams != null) {
|
||||
attributes.addAll(parentLayoutParams.attributes);
|
||||
}
|
||||
|
||||
final var paramSuperclasses = widgetInfo.getLayoutParamSuperClasses(parentWidget.name);
|
||||
if (paramSuperclasses != null) {
|
||||
for (var superclass : paramSuperclasses) {
|
||||
if ("java.lang.Object".equals(superclass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var split = superclass.split("\\.");
|
||||
final var simpleClassName = split[split.length - 2];
|
||||
var paramName = split[split.length - 1];
|
||||
paramName = paramName.substring(0, paramName.length() - "Params".length());
|
||||
final var superParamEntry = attrs.getStyle(simpleClassName + "_" + paramName);
|
||||
if (superParamEntry != null) {
|
||||
attributes.addAll(superParamEntry.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var sheet = getAttrListSheet();
|
||||
sheet.setItems(filterAppliedAttributes(attributes));
|
||||
sheet.show(getChildFragmentManager(), "attr_list_sheet");
|
||||
|
||||
} else if (position == 1) { // Delete
|
||||
DialogUtils.newYesNoDialog(
|
||||
getContext(),
|
||||
(dialog, which) -> {
|
||||
var handled = selectedView.removeFromParent();
|
||||
if (!handled) {
|
||||
handled =
|
||||
mDeletionFailedListener != null
|
||||
&& mDeletionFailedListener.onDeletionFailed(this.selectedView);
|
||||
}
|
||||
if (!handled) {
|
||||
ToastUtilsKt.toast(getString(string.msg_view_deletion_failed), Toaster.Type.ERROR);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
},
|
||||
(dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
} else if (position == 2) { // Select parent
|
||||
if (this.selectedView.getParent() == null) {
|
||||
ToastUtilsKt.toast(getString(string.msg_no_view_parent), Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedView = this.selectedView.getParent();
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
setupViewData();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
private List<Attr> filterAppliedAttributes(@NonNull TreeSet<Attr> attributes) {
|
||||
attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name));
|
||||
return new ArrayList<>(attributes);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private AttributeListSheet getAttrListSheet() {
|
||||
if (mAttrListSheet == null) {
|
||||
mAttrListSheet = new AttributeListSheet();
|
||||
mAttrListSheet.setCancelable(true);
|
||||
}
|
||||
|
||||
return mAttrListSheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Attr attr) {
|
||||
addNewAttribute(attr);
|
||||
}
|
||||
|
||||
private void addNewAttribute(@NonNull Attr attr) {
|
||||
final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false);
|
||||
attribute.setAttr(attr);
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onValueChanged(@NonNull IAttribute attribute, String newValue) {
|
||||
if (dismissIfSelectedNull()) {
|
||||
LOG.warn("Cannot update attribute with new value. Selected view is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var attributeName = attribute.getAttributeName();
|
||||
final var namespace = attribute.getNamespace();
|
||||
|
||||
if (this.selectedView.hasAttribute(namespace, attributeName)) {
|
||||
this.selectedView.updateAttribute(namespace, attributeName, newValue);
|
||||
} else {
|
||||
final var attr = new UiAttribute(namespace, attributeName, newValue);
|
||||
this.selectedView.addAttribute(attr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener can be used to get notified when we fail to remove a view from its parent. This is
|
||||
* used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
|
||||
*
|
||||
* <p>When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet
|
||||
* fails to remove the view from its parent ({@link IView#getParent()} is null for root XML
|
||||
* layout). In this case, DesignerActivity check if the view that we were trying to delete is the
|
||||
* root layout or not. If it is the root layout, then it deletes the layout from the layout
|
||||
* container.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface OnViewDeletionFailedListener {
|
||||
|
||||
/**
|
||||
* @param view The view that was not removed from its parent.
|
||||
* @return {@code true} if the listener handled the error. {@code false} otherwise.
|
||||
*/
|
||||
boolean onDeletionFailed(IView view);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.sheets;
|
||||
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_BOOLEAN;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_COLOR;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_DIMENSION;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_ENUM;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLAG;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLOAT;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_FRACTION;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_INTEGER;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_REFERENCE;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.fragments.attr.BooleanEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ColorEditor;
|
||||
import com.itsaky.androidide.fragments.attr.DimensionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.EnumEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FlagEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FloatEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FractionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.IntegerEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ReferenceEditor;
|
||||
import com.itsaky.androidide.fragments.attr.StringEditor;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Sheet for editing value of an attribute.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrValueEditorSheet extends BottomSheetDialogFragment
|
||||
implements BaseValueEditorFragment.OnValueChangeListener {
|
||||
|
||||
private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute";
|
||||
private BaseValueEditorFragment.OnValueChangeListener valueChangeListener;
|
||||
private LayoutAttrValueEditorBinding binding;
|
||||
private AttrValueFormatTabAdapter adapter;
|
||||
private XMLAttribute attribute;
|
||||
|
||||
@NonNull
|
||||
public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
|
||||
final var args = new Bundle();
|
||||
args.putParcelable(KEY_ATTRIBUTE, attribute);
|
||||
|
||||
final var fragment = new AttrValueEditorSheet();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (attribute == null) {
|
||||
this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(this.attribute);
|
||||
|
||||
final var namespace = this.attribute.getNamespace();
|
||||
final var formatText = Attr.createFormatText(attribute.getFormat());
|
||||
this.binding.attributeName.setText(
|
||||
String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName()));
|
||||
this.binding.attributeFormat.setText(formatText);
|
||||
|
||||
setupTabs(formatText);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void setupTabs(@NonNull String formatText) {
|
||||
this.binding.tabs.removeAllTabs();
|
||||
this.binding.pager.setSaveEnabled(false);
|
||||
|
||||
if (this.adapter != null) {
|
||||
this.adapter.removeAll();
|
||||
this.adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute);
|
||||
|
||||
final var formats = formatText.split("\\|");
|
||||
for (var formatName : formats) {
|
||||
if (TextUtils.isEmpty(formatName) || formatName.length() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var frag = getFragmentClass(formatName);
|
||||
if (frag != null) {
|
||||
this.adapter.addFragment(frag, formatName);
|
||||
}
|
||||
}
|
||||
|
||||
binding.pager.setAdapter(this.adapter);
|
||||
binding.tabs.setupWithViewPager(binding.pager);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract(pure = true)
|
||||
private Class<? extends BaseValueEditorFragment> getFragmentClass(@NonNull String name) {
|
||||
switch (name) {
|
||||
case FORMAT_REFERENCE:
|
||||
return ReferenceEditor.class;
|
||||
case FORMAT_COLOR:
|
||||
return ColorEditor.class;
|
||||
case FORMAT_BOOLEAN:
|
||||
return BooleanEditor.class;
|
||||
case FORMAT_DIMENSION:
|
||||
return DimensionEditor.class;
|
||||
case FORMAT_FLOAT:
|
||||
return FloatEditor.class;
|
||||
case FORMAT_INTEGER:
|
||||
return IntegerEditor.class;
|
||||
case FORMAT_FRACTION:
|
||||
return FractionEditor.class;
|
||||
case FORMAT_STRING:
|
||||
return StringEditor.class;
|
||||
case FORMAT_ENUM:
|
||||
return EnumEditor.class;
|
||||
case FORMAT_FLAG:
|
||||
return FlagEditor.class;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAttribute(XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onValueChanged(IAttribute attribute, String newValue) {
|
||||
if (valueChangeListener != null) {
|
||||
valueChangeListener.onValueChanged(attribute, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.adapters.AttrListAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAddAttrSheetBinding;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A bottom sheet dialog for showing a simple list of text icon pair.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
* @see com.itsaky.androidide.models.IconTextListItem
|
||||
*/
|
||||
public class AttributeListSheet extends BottomSheetDialogFragment {
|
||||
|
||||
private LayoutAddAttrSheetBinding binding;
|
||||
private Consumer<Attr> clickConsumer;
|
||||
private List<Attr> mItems;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
binding.searchBar.setOnQueryTextListener(
|
||||
new SearchView.OnQueryTextListener() {
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return filterAttributes(query.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
return filterAttributes(newText.trim());
|
||||
}
|
||||
});
|
||||
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems));
|
||||
}
|
||||
|
||||
private boolean filterAttributes(@NonNull String query) {
|
||||
if (mItems == null || TextUtils.isEmpty(query)) {
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
final var filtered =
|
||||
this.mItems.stream()
|
||||
.filter(
|
||||
attr -> attr.name.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
update(filtered);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
update(mItems);
|
||||
}
|
||||
|
||||
public void update(List<Attr> items) {
|
||||
if (binding != null) {
|
||||
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items));
|
||||
}
|
||||
}
|
||||
|
||||
public void setItems(@Nullable List<Attr> items) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
|
||||
mItems = items;
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
//noinspection unchecked
|
||||
this.clickConsumer = (Consumer<Attr>) getParentFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard);
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
package com.itsaky.androidide.language.xml
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Color.parseColor
|
||||
import com.google.common.collect.EvictingQueue
|
||||
import com.itsaky.androidide.app.IDEApplication
|
||||
import com.itsaky.androidide.language.incremental.BaseIncrementalAnalyzeManager
|
||||
import com.itsaky.androidide.language.incremental.IncrementalToken
|
||||
import com.itsaky.androidide.language.incremental.LineState
|
||||
@@ -48,10 +48,10 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATOR
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.TEXT_NORMAL
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.XML_TAG
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment
|
||||
import com.itsaky.inflater.util.CommonParseUtils
|
||||
import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager.LineTokenizeResult
|
||||
import io.github.rosemoe.sora.lang.styling.Span
|
||||
import io.github.rosemoe.sora.lang.styling.TextStyle.makeStyle
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Syntax analyzer for XML.
|
||||
@@ -85,7 +85,7 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
|
||||
override fun popTokensAfterIncomplete(
|
||||
incompleteToken: IncrementalToken,
|
||||
tokens: MutableList<IncrementalToken>
|
||||
tokens: MutableList<IncrementalToken>,
|
||||
) {
|
||||
// Do nothing
|
||||
}
|
||||
@@ -93,8 +93,6 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
override fun generateSpans(
|
||||
tokens: LineTokenizeResult<LineState, IncrementalToken>,
|
||||
): MutableList<Span> {
|
||||
val app = IDEApplication.getInstance()
|
||||
val parser = CommonParseUtils(app.resourceTable, app.resources.displayMetrics)
|
||||
val spans = mutableListOf<Span>()
|
||||
var previous = XMLLexer.SEA_WS
|
||||
|
||||
@@ -109,7 +107,7 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
COMMENT_START,
|
||||
COMMENT_END,
|
||||
CommentModeEnd,
|
||||
CommentText -> spans.add(Span.obtain(offset, forComment()))
|
||||
CommentText, -> spans.add(Span.obtain(offset, forComment()))
|
||||
OPEN,
|
||||
DASH,
|
||||
NOT,
|
||||
@@ -126,8 +124,7 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
val text: String = token.text
|
||||
val textVar = text.replace("\"", "")
|
||||
if (isColorValue(textVar)) {
|
||||
val color: Int =
|
||||
parser.parseColor(textVar, IDEApplication.getInstance().applicationContext)
|
||||
val color: Int = parseColor(textVar)
|
||||
val span = Span.obtain(offset + 1, makeStyle(LITERAL))
|
||||
span.setUnderlineColor(color)
|
||||
spans.add(span)
|
||||
@@ -163,8 +160,7 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
try {
|
||||
val textVar: String = token.text
|
||||
if (isColorValue(textVar)) {
|
||||
val color: Int =
|
||||
parser.parseColor(textVar, IDEApplication.getInstance().applicationContext)
|
||||
val color: Int = parseColor(textVar)
|
||||
val span = Span.obtain(offset, makeStyle(TEXT_NORMAL))
|
||||
span.setUnderlineColor(color)
|
||||
spans.add(span)
|
||||
@@ -187,8 +183,7 @@ class XMLAnalyzer : BaseIncrementalAnalyzeManager(XMLLexer::class.java) {
|
||||
override fun handleIncompleteToken(token: IncrementalToken) {}
|
||||
|
||||
private fun isColorValue(value: String): Boolean {
|
||||
return (value.startsWith("#") ||
|
||||
value.startsWith("@android:color") ||
|
||||
value.startsWith("@color/"))
|
||||
return hexColorMatcher.matcher(value).matches()
|
||||
}
|
||||
private val hexColorMatcher: Pattern = Pattern.compile("#[a-fA-F\\d]{6,8}")
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Holds data about the widget item in widgets list shown in UI Designer screen.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class UIWidget implements IconTextListItem, Parcelable {
|
||||
|
||||
public static final Creator<UIWidget> CREATOR =
|
||||
new Creator<UIWidget>() {
|
||||
@Override
|
||||
public UIWidget createFromParcel(Parcel in) {
|
||||
return new UIWidget(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UIWidget[] newArray(int size) {
|
||||
return new UIWidget[size];
|
||||
}
|
||||
};
|
||||
private final String name;
|
||||
private final int icon;
|
||||
private final String clazz;
|
||||
|
||||
private UIWidget(Parcel in) {
|
||||
this.name = in.readString();
|
||||
this.icon = in.readInt();
|
||||
this.clazz = in.readString();
|
||||
}
|
||||
|
||||
public UIWidget(String name, @DrawableRes int icon, Class<?> clazz) {
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.clazz = clazz.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName(), getIcon(), clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
UIWidget widget = (UIWidget) o;
|
||||
return getIcon() == widget.getIcon()
|
||||
&& getName().equals(widget.getName())
|
||||
&& clazz.equals(widget.clazz);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class of this widget.
|
||||
*
|
||||
* @return The class that this widget represents
|
||||
* @throws ClassNotFoundException If there is no class with this widget's class name. However,
|
||||
* this will never be thrown.
|
||||
*/
|
||||
public Class<? extends View> asClass() throws ClassNotFoundException {
|
||||
return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class);
|
||||
}
|
||||
|
||||
public String getWidgetClassName() {
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconResource() {
|
||||
return getIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(name);
|
||||
dest.writeInt(icon);
|
||||
dest.writeString(clazz);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Model class for holding data about a group that will be displayed in Widgets list in UI Designer
|
||||
* screen.
|
||||
*
|
||||
* <p>A UIWidgetGroup contains multiple {@link UIWidget}.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class UIWidgetGroup implements IconTextListItem, Parcelable {
|
||||
|
||||
public static final Creator<UIWidgetGroup> CREATOR =
|
||||
new Creator<UIWidgetGroup>() {
|
||||
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public UIWidgetGroup createFromParcel(Parcel in) {
|
||||
return new UIWidgetGroup(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@Override
|
||||
public UIWidgetGroup[] newArray(int size) {
|
||||
return new UIWidgetGroup[size];
|
||||
}
|
||||
};
|
||||
/** Name of this group. */
|
||||
private final String name;
|
||||
/** Children of this group. */
|
||||
private final List<UIWidget> children;
|
||||
/** Is this group currently selected in the group list? */
|
||||
private boolean selected;
|
||||
|
||||
public UIWidgetGroup(String name) {
|
||||
this.name = name;
|
||||
this.selected = false;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
protected UIWidgetGroup(@NonNull Parcel in) {
|
||||
this.name = in.readString();
|
||||
this.children = in.createTypedArrayList(UIWidget.CREATOR);
|
||||
this.selected = in.readByte() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this widget to this group. Does nothing if the child is already present.
|
||||
*
|
||||
* @param child The child to add.
|
||||
* @return This instance for chained method calls.
|
||||
*/
|
||||
public UIWidgetGroup addChild(UIWidget child) {
|
||||
if (children.contains(child)) {
|
||||
return this;
|
||||
}
|
||||
this.children.add(child);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child at the given index.
|
||||
*
|
||||
* @param index The index of the child.
|
||||
* @return The child at the index.
|
||||
* @throws IndexOutOfBoundsException If the given index is not valid.
|
||||
*/
|
||||
public UIWidget getChild(int index) {
|
||||
return children.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName(), getChildren(), isSelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
UIWidgetGroup group = (UIWidgetGroup) o;
|
||||
return isSelected() == group.isSelected()
|
||||
&& getName().equals(group.getName())
|
||||
&& getChildren().equals(group.getChildren());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this group.
|
||||
*
|
||||
* @return The name of this group.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of this group.
|
||||
*
|
||||
* @return The children.
|
||||
*/
|
||||
@NonNull
|
||||
public List<UIWidget> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconResource() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(name);
|
||||
dest.writeTypedList(children);
|
||||
dest.writeByte((byte) (selected ? 1 : 0));
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.IDEApplication;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A model that is used to show attributes list of a view.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class XMLAttribute extends UiAttribute implements Parcelable {
|
||||
|
||||
public static final Creator<XMLAttribute> CREATOR =
|
||||
new Creator<>() {
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public XMLAttribute createFromParcel(Parcel in) {
|
||||
return new XMLAttribute(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(value = "_ -> new", pure = true)
|
||||
@Override
|
||||
public XMLAttribute[] newArray(int size) {
|
||||
return new XMLAttribute[size];
|
||||
}
|
||||
};
|
||||
public Attr attr;
|
||||
/** Is this attribute applied to any view? */
|
||||
private boolean isApplied;
|
||||
/**
|
||||
* Format of this attribute. May be stored here so we don't have to look in {@link
|
||||
* com.itsaky.attrinfo.AttrInfo AttrInfo} again and again.
|
||||
*/
|
||||
private int format = -1;
|
||||
|
||||
public XMLAttribute(IAttribute attribute) {
|
||||
this(attribute, true);
|
||||
}
|
||||
|
||||
public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) {
|
||||
this(attribute.getNamespace(), attribute.getAttributeName(), attribute.getValue(), isApplied);
|
||||
}
|
||||
|
||||
public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) {
|
||||
super(namespace, name, value);
|
||||
this.isApplied = isApplied;
|
||||
}
|
||||
|
||||
private XMLAttribute(@NonNull Parcel in) {
|
||||
this(
|
||||
in.readParcelable(INamespace.class.getClassLoader()),
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
in.readByte() != 0);
|
||||
this.attr = in.readParcelable(Attr.class.getClassLoader());
|
||||
this.format = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. {@link #isApplied()} will return
|
||||
* true by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @param value The value of the attribute.
|
||||
* @return A new instance of this class with the provided data.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_, _ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name, String value) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute instance with the android namespace. The {@link #isApplied()} will
|
||||
* return {@code false} by default.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @return The new attribute instance.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_ -> new")
|
||||
public static XMLAttribute newAndroidAttribute(String name) {
|
||||
return new XMLAttribute(INamespace.ANDROID, name, "", false);
|
||||
}
|
||||
|
||||
public int findFormat() {
|
||||
|
||||
if (getFormat() > 0) {
|
||||
return getFormat();
|
||||
}
|
||||
|
||||
final var attrs = IDEApplication.getInstance().attrInfo();
|
||||
this.attr = attrs.getAttribute(getAttributeName());
|
||||
if (this.attr != null) {
|
||||
this.format = this.attr.format;
|
||||
}
|
||||
|
||||
return getFormat();
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(int format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public boolean hasFormat(int format) {
|
||||
return (this.format & format) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(String value) {
|
||||
super.apply(value);
|
||||
isApplied = true;
|
||||
}
|
||||
|
||||
public boolean isApplied() {
|
||||
return isApplied;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(getNamespace(), flags);
|
||||
dest.writeString(getAttributeName());
|
||||
dest.writeString(getValue());
|
||||
dest.writeByte((byte) (isApplied ? 1 : 0));
|
||||
dest.writeParcelable(getAttr(), flags);
|
||||
dest.writeInt(this.format);
|
||||
}
|
||||
|
||||
public Attr getAttr() {
|
||||
return attr;
|
||||
}
|
||||
|
||||
public void setAttr(Attr attr) {
|
||||
Objects.requireNonNull(attr);
|
||||
this.attr = attr;
|
||||
setFormat(attr.format);
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.projects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ProjectResourceTable implements IResourceTable {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder");
|
||||
private File resDir;
|
||||
private File color;
|
||||
private File layout;
|
||||
private File[] drawables;
|
||||
private File[] mipmaps;
|
||||
|
||||
@Override
|
||||
public File findDrawable(@NonNull String name) {
|
||||
for (File drawable : drawables) {
|
||||
final File file = findFileWithName(drawable.listFiles(), name);
|
||||
if (file != null) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File findLayout(@NonNull String name) {
|
||||
return findFileWithName(this.layout.listFiles(), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findString(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findString(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@string/")) {
|
||||
var ref = value.substring("@string/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findColor(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findColor(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@color/")) {
|
||||
var ref = value.substring("@color/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] findArray(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
if (table == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
final var resource = table.findArray(name);
|
||||
if (resource == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
return resource.getValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findDimension(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findDimension(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@dimen/")) {
|
||||
var ref = value.substring("@dimen/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findBoolean(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findBoolean(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@bool/")) {
|
||||
final var ref = val.substring("@bool/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findInteger(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findInteger(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@integer/")) {
|
||||
final var ref = val.substring("@integer/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInflatingFile(@NonNull File file) {
|
||||
if (file.getParentFile() == null) {
|
||||
throw new IllegalArgumentException("Invalid inflating file");
|
||||
}
|
||||
|
||||
setupDirectories(
|
||||
file // layout file
|
||||
.getParentFile() // layout dir
|
||||
.getParentFile()); // res dir
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> listResourceNames(@Nullable String type) {
|
||||
if (type == null) {
|
||||
return listAllResourceNames();
|
||||
} else {
|
||||
final var set = new HashSet<String>();
|
||||
switch (type) {
|
||||
case "drawable":
|
||||
for (var folder : this.drawables) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
case "layout":
|
||||
listAndAddTo(set, this.layout, type);
|
||||
break;
|
||||
case "color":
|
||||
listAndAddTo(set, this.color, type);
|
||||
break;
|
||||
case "mipmap":
|
||||
for (var folder : this.mipmaps) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDirectories(File resDir) {
|
||||
|
||||
if (resDir == null) {
|
||||
LOG.error("Null resource directory passed to resource finder. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.resDir = resDir;
|
||||
this.color = new File(resDir, "color");
|
||||
this.layout = new File(resDir, "layout");
|
||||
|
||||
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
|
||||
this.drawables = drawables == null ? new File[0] : drawables;
|
||||
|
||||
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
|
||||
this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
|
||||
}
|
||||
|
||||
private File findFileWithName(File[] files, String name) {
|
||||
if (files == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
var simpleName = file.getName();
|
||||
|
||||
if (simpleName.startsWith(name)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void listAndAddTo(HashSet<String> set, @NonNull File dir, String type) {
|
||||
final var files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var file : files) {
|
||||
var name = file.getName();
|
||||
if (!name.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
name = String.format(Locale.ROOT, "@%s/%s", type, name);
|
||||
set.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Collection<String> listAllResourceNames() {
|
||||
final var set = new HashSet<>(listResourceNames("color"));
|
||||
set.addAll(listResourceNames("drawable"));
|
||||
set.addAll(listResourceNames("mipmap"));
|
||||
set.addAll(listResourceNames("layout"));
|
||||
return set;
|
||||
}
|
||||
|
||||
private static class NameStartsWith implements FileFilter {
|
||||
|
||||
private final String name;
|
||||
|
||||
public NameStartsWith(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(@NonNull File file) {
|
||||
return file.getName().startsWith(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.ui;
|
||||
|
||||
import com.itsaky.androidide.models.UIWidget;
|
||||
import com.itsaky.inflater.IView;
|
||||
|
||||
/**
|
||||
* Store data about a draggable widget. Used in UI Designer.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class WidgetDragData {
|
||||
|
||||
/**
|
||||
* Set this to true if the view that is being dragged was already inflated in the layout. If this
|
||||
* is set to {@code true}, {@link #alreadyInflatedView} must NOT be {@code null}.
|
||||
*
|
||||
* <p>If this is {@code false}, then {@link #newDragData} must be not be null.
|
||||
*/
|
||||
public boolean isAlreadyInflated;
|
||||
|
||||
/**
|
||||
* The view that was already inflated and is being relocated. This must not be null if {@link
|
||||
* #isAlreadyInflated} is {@code true}.
|
||||
*/
|
||||
public IView alreadyInflatedView;
|
||||
|
||||
/** The UIWidget that is being dragged. */
|
||||
public UIWidget newDragData;
|
||||
|
||||
private WidgetDragData() {}
|
||||
|
||||
public WidgetDragData(
|
||||
boolean isAlreadyInflated, IView alreadyInflatedView, UIWidget newDragData) {
|
||||
this.isAlreadyInflated = isAlreadyInflated;
|
||||
this.alreadyInflatedView = alreadyInflatedView;
|
||||
this.newDragData = newDragData;
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.RectF;
|
||||
import android.view.DragEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.itsaky.androidide.DesignerActivity;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.models.UIWidget;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IView;
|
||||
import com.itsaky.inflater.IViewGroup;
|
||||
import com.itsaky.inflater.impl.ErrorUiView;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.inflater.impl.UiView;
|
||||
import com.itsaky.inflater.impl.UiViewGroup;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* Common drag listener for handling drag events in all ViewGroups in the UI Designer.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class WidgetDragListener implements View.OnDragListener {
|
||||
|
||||
public static final String ANDROID_NS = "android";
|
||||
private static final ILogger LOG = ILogger.newInstance("WidgetDragListener");
|
||||
private final IView placeholder;
|
||||
private final IViewGroup viewGroup;
|
||||
private final OnViewAddedListener addedListener;
|
||||
private final int PLACEHOLDER_HEIGHT = 20; // in dp
|
||||
private final int PLACEHOLDER_WIDTH = 40; // in dp
|
||||
|
||||
public WidgetDragListener(
|
||||
@NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) {
|
||||
this.placeholder = new UiView("android.view.View", new View(context), true);
|
||||
this.viewGroup = viewGroup;
|
||||
this.addedListener = addedListener;
|
||||
this.placeholder.asView().setBackgroundResource(R.drawable.bg_widget_drag_placeholder);
|
||||
this.placeholder
|
||||
.asView()
|
||||
.setLayoutParams(
|
||||
new ViewGroup.LayoutParams(
|
||||
SizeUtils.dp2px(PLACEHOLDER_WIDTH), SizeUtils.dp2px(PLACEHOLDER_HEIGHT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDrag(View v, @NonNull DragEvent event) {
|
||||
|
||||
if (this.viewGroup instanceof ErrorUiView) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var action = event.getAction();
|
||||
final var clipDesc = event.getClipDescription();
|
||||
final var dragData = (WidgetDragData) event.getLocalState();
|
||||
|
||||
var handled = true;
|
||||
switch (action) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
handled = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME);
|
||||
break;
|
||||
case DragEvent.ACTION_DRAG_ENTERED:
|
||||
case DragEvent.ACTION_DRAG_LOCATION:
|
||||
this.placeholder.removeFromParent(); // If placeholder is added in another group, remove it.
|
||||
this.viewGroup.addView(this.placeholder, findPlaceHolderIndex(event.getX(), event.getY()));
|
||||
break;
|
||||
case DragEvent.ACTION_DRAG_EXITED:
|
||||
this.placeholder.removeFromParent();
|
||||
break;
|
||||
case DragEvent.ACTION_DROP:
|
||||
final var index = this.viewGroup.indexOfChild(this.placeholder);
|
||||
this.placeholder.removeFromParent();
|
||||
|
||||
var view =
|
||||
dragData.isAlreadyInflated
|
||||
? dragData.alreadyInflatedView
|
||||
: createView(dragData.newDragData);
|
||||
if (!dragData.isAlreadyInflated) {
|
||||
applyBasicAttributes(view);
|
||||
}
|
||||
|
||||
// Remove from parent if it has a parent
|
||||
view.removeFromParent();
|
||||
this.viewGroup.addView(view, index);
|
||||
|
||||
if (this.addedListener != null) {
|
||||
this.addedListener.onViewAdded(view);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private void applyBasicAttributes(@NonNull IView view) {
|
||||
view.addAttribute(layoutWidthAttr());
|
||||
view.addAttribute(layoutHeightAttr());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(" -> new")
|
||||
private IAttribute layoutHeightAttr() {
|
||||
return new UiAttribute(INamespace.ANDROID, "layout_height", "wrap_content");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Contract(" -> new")
|
||||
private IAttribute layoutWidthAttr() {
|
||||
return new UiAttribute(INamespace.ANDROID, "layout_width", "wrap_content");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index at which the placeholder will be added in the current view group.
|
||||
*
|
||||
* @param x The x coordinate of the touch event
|
||||
* @param y The y coordinate of the touch event
|
||||
* @return The index at which the placeholder of the view should be added. The returned index is
|
||||
* always valid index in the {@link #viewGroup}.
|
||||
*/
|
||||
private int findPlaceHolderIndex(final float x, final float y) {
|
||||
final var count = this.viewGroup.getChildCount();
|
||||
for (int i = 0; i < this.viewGroup.getChildCount(); i++) {
|
||||
final var child = this.viewGroup.getChildAt(i);
|
||||
final var rect = getViewRect(child);
|
||||
if (rect.contains(x, y)) {
|
||||
final var top = topHalf(rect);
|
||||
final var bottom = bottomHalf(rect);
|
||||
if (top.contains(x, y)) {
|
||||
return Math.max(0, i - 1);
|
||||
} else if (bottom.contains(x, y)) {
|
||||
return Math.min(count, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't find a suitable index, return the last index
|
||||
return count;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RectF getViewRect(@NonNull IView view) {
|
||||
final var v = view.asView();
|
||||
|
||||
final var rect = new RectF();
|
||||
rect.left = v.getLeft();
|
||||
rect.top = v.getTop();
|
||||
rect.right = rect.left + v.getWidth();
|
||||
rect.bottom = rect.top + v.getHeight();
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private RectF topHalf(RectF src) {
|
||||
final RectF result = new RectF(src);
|
||||
result.bottom -= result.height() / 2;
|
||||
return src;
|
||||
}
|
||||
|
||||
private RectF bottomHalf(RectF src) {
|
||||
final RectF result = new RectF(src);
|
||||
result.top += result.height() / 2;
|
||||
return src;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private IView createView(@NonNull UIWidget widget) {
|
||||
try {
|
||||
final var clazz = widget.asClass();
|
||||
final var constructor = clazz.getConstructor(Context.class);
|
||||
constructor.setAccessible(true);
|
||||
final var instance = constructor.newInstance(this.viewGroup.asView().getContext());
|
||||
|
||||
if (instance instanceof ViewGroup) {
|
||||
return new UiViewGroup(clazz.getName(), (ViewGroup) instance);
|
||||
} else {
|
||||
return new UiView(clazz.getName(), instance);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to add widget", e);
|
||||
throw new RuntimeException("Unable to add widget", e);
|
||||
}
|
||||
}
|
||||
|
||||
private RectF leftHalf(RectF src) {
|
||||
final RectF result = new RectF(src);
|
||||
result.right -= result.width() / 2;
|
||||
return src;
|
||||
}
|
||||
|
||||
private RectF rightHalf(RectF src) {
|
||||
final RectF result = new RectF(src);
|
||||
result.left += result.width() / 2;
|
||||
return src;
|
||||
}
|
||||
|
||||
public static interface OnViewAddedListener {
|
||||
void onViewAdded(IView view);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.ui;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.view.View;
|
||||
|
||||
public class WidgetDragShadowBuilder extends View.DragShadowBuilder {
|
||||
|
||||
private final double TOUCH_Y_OFFSET = 0.3;
|
||||
|
||||
public WidgetDragShadowBuilder(View view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
|
||||
if (getView() != null) {
|
||||
final var width = getView().getWidth();
|
||||
final var height = getView().getHeight();
|
||||
outShadowSize.set(width, height);
|
||||
outShadowTouchPoint.set(width / 2, height + (int) (height * TOUCH_Y_OFFSET));
|
||||
} else {
|
||||
super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.inflater.IView;
|
||||
|
||||
/**
|
||||
* A touch listener for handling touch events of a view in the UI Designer.
|
||||
*
|
||||
* <p>Some of the views have no effect of setting a click listener or a long click listener (e.g.
|
||||
* SeekBar). In order to fix this, we use {@link android.view.GestureDetector} and manually handle
|
||||
* click events.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class WidgetTouchListener extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnTouchListener {
|
||||
|
||||
private final GestureDetector mGestureDetector;
|
||||
private final OnClickListener clickListener;
|
||||
private final OnLongClickListener longClickListener;
|
||||
|
||||
private final IView mView;
|
||||
|
||||
public WidgetTouchListener(
|
||||
@NonNull IView view,
|
||||
@Nullable OnClickListener clickListener,
|
||||
@Nullable OnLongClickListener longClickListener) {
|
||||
this.mView = view;
|
||||
this.mGestureDetector = new GestureDetector(view.asView().getContext(), this);
|
||||
this.clickListener = clickListener;
|
||||
this.longClickListener = longClickListener;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View view, @NonNull MotionEvent motionEvent) {
|
||||
return mGestureDetector.onTouchEvent(motionEvent);
|
||||
}
|
||||
|
||||
// TODO Should we use onSingleTapConfirmed instead?
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
if (this.clickListener != null) {
|
||||
this.clickListener.onClick(this.mView);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
|
||||
if (this.mView.getParent() == null) {
|
||||
// This is the root layout so this should not be draggable
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.longClickListener != null && this.longClickListener.onLongClick(this.mView)) {
|
||||
this.mView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return true; // We would like to get notified about further events.
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener which is invoked when we detect a single click event.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface OnClickListener {
|
||||
void onClick(IView view);
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener which is invoked when we detect a long click event.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface OnLongClickListener {
|
||||
boolean onLongClick(IView view);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.ui.edit;
|
||||
|
||||
import com.itsaky.androidide.app.IDEApplication;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.values.ValuesTable;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Base class for attribute edit handlers.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class BaseSuggestionProvider implements IValueSuggestionProvider {
|
||||
|
||||
protected final File resDir;
|
||||
protected final File layout;
|
||||
|
||||
protected BaseSuggestionProvider(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
final var path = layout.getAbsolutePath();
|
||||
if (!Pattern.compile(".*/src/.*/res/layout/(\\w|_)+\\.xml").matcher(path).matches()) {
|
||||
throw new IllegalArgumentException("Given layout file is not valid");
|
||||
}
|
||||
|
||||
this.layout = layout;
|
||||
this.resDir = Objects.requireNonNull(layout.getParentFile()).getParentFile();
|
||||
Objects.requireNonNull(this.resDir);
|
||||
}
|
||||
|
||||
protected ValuesTable getValuesTable() {
|
||||
return ValuesTableFactory.getTable(this.resDir);
|
||||
}
|
||||
|
||||
protected IResourceTable getResourceTable() {
|
||||
return IDEApplication.getInstance().getResourceTable();
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.ui.edit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.androidide.lsp.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles editing dimension attributes.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionSuggestionProvider extends BaseSuggestionProvider {
|
||||
|
||||
/** The dimension units that will be suggested. */
|
||||
private final List<String> dimensions = new ArrayList<>();
|
||||
|
||||
public DimensionSuggestionProvider(File layout) {
|
||||
super(layout);
|
||||
|
||||
this.dimensions.add("dp");
|
||||
this.dimensions.add("sp");
|
||||
this.dimensions.add("px");
|
||||
this.dimensions.add("pt");
|
||||
this.dimensions.add("in");
|
||||
this.dimensions.add("mm");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFormat(int format) {
|
||||
return (format & Attr.DIMENSION) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<String> suggest(IAttribute attribute, @NonNull String prefix) {
|
||||
final var list = new ArrayList<String>();
|
||||
if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) {
|
||||
// Fixed dimension values
|
||||
// e.g. 10dp
|
||||
|
||||
var val = extractDimensionVal(prefix);
|
||||
for (var unit : this.dimensions) {
|
||||
final var dimension = val + unit;
|
||||
if (StringUtils.matchesFuzzy(dimension, prefix, true)) {
|
||||
list.add(dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private int extractDimensionVal(@NonNull String prefix) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
var i = 0;
|
||||
while (i < prefix.length()) {
|
||||
if (Character.isDigit(prefix.charAt(i))) {
|
||||
s.append(prefix.charAt(i));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return Integer.parseInt(s.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.ui.edit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An attribute value provider provides suggestions while editing an attribute value.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IValueSuggestionProvider {
|
||||
|
||||
/**
|
||||
* Check if the given format is supported by this provider.
|
||||
*
|
||||
* @param format The formats to check.
|
||||
* @return <code>true</code> if the given formats contain a supported format, <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
boolean checkFormat(int format);
|
||||
|
||||
/**
|
||||
* Provide suggestions for the given attribute.
|
||||
*
|
||||
* @param attribute The attribute to provide suggestions for.
|
||||
* @param prefix The prefix (partial identifier) to filter the suggestions.
|
||||
* @return The suggestion items. Must not be <code>null</code>.
|
||||
*/
|
||||
@NonNull
|
||||
List<String> suggest(IAttribute attribute, String prefix);
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.FLA
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.FLAG_WS_TRAILING;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.FONT_LIGATURES;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.FONT_SIZE;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.USE_CUSTOM_FONT;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.USE_ICU;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.USE_MAGNIFER;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.USE_CUSTOM_FONT;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.WORD_WRAP;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.getDrawEmptyLineWs;
|
||||
import static com.itsaky.androidide.preferences.internal.EditorPreferencesKt.getDrawInnerWs;
|
||||
@@ -70,7 +70,6 @@ import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.utils.TypefaceUtilsKt;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@@ -78,7 +77,6 @@ import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.rosemoe.sora.lang.EmptyLanguage;
|
||||
@@ -385,9 +383,6 @@ public class CodeEditorView extends LinearLayout {
|
||||
|
||||
private void notifySaved() {
|
||||
binding.editor.dispatchDocumentSaveEvent();
|
||||
if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) {
|
||||
ValuesTableFactory.syncWithFile(getFile());
|
||||
}
|
||||
}
|
||||
|
||||
public LayoutCodeEditorBinding getBinding() {
|
||||
@@ -422,7 +417,7 @@ public class CodeEditorView extends LinearLayout {
|
||||
LOG.warn("Editor layout is null content=" + binding + ", searchLayout=" + searchLayout);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
searchLayout.beginSearchMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout 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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
tools:context=".DesignerActivity">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:title="@string/app_name" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/label_ui_designer_content"
|
||||
android:fillViewport="true"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="32dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="32dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
<com.itsaky.androidide.views.DesignerLayoutContainer
|
||||
android:id="@+id/layout_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_design_container_background"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/navigation_contents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2.0">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/group_items"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.7"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/layout_simple_icon_text" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/widget_items"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.3"
|
||||
android:background="@color/bg_widget_selected"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/layout_simple_icon_text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
+19
-1
@@ -19,14 +19,17 @@ package com.itsaky.androidide.aaptcompiler
|
||||
|
||||
import com.android.aaptcompiler.BlameLogger
|
||||
import com.android.aaptcompiler.ResourceCompilerOptions
|
||||
import com.android.aaptcompiler.ResourceFile
|
||||
import com.android.aaptcompiler.ResourceName
|
||||
import com.android.aaptcompiler.XmlProcessor
|
||||
import com.android.aaptcompiler.compileResource
|
||||
import com.android.aaptcompiler.extractPathData
|
||||
import com.android.utils.StdLogger
|
||||
import com.android.utils.StdLogger.Level.VERBOSE
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.absolute
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
/** @author Akash Yadav */
|
||||
@@ -45,4 +48,19 @@ class CompilerTest {
|
||||
compileResource(input, output, ResourceCompilerOptions(), BlameLogger(StdLogger(VERBOSE)))
|
||||
println("Compiled in ${System.nanoTime() - start} nano seconds")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test xml processor`() {
|
||||
val input =
|
||||
Paths.get(".", "src/test/resources/layout/activity_editor.xml").absolute().normalize().toFile()
|
||||
val pathData = extractPathData(input, input.absolutePath)
|
||||
val resFile = ResourceFile(
|
||||
ResourceName("", pathData.type!!, pathData.name),
|
||||
pathData.config,
|
||||
pathData.source,
|
||||
ResourceFile.Type.ProtoXml
|
||||
)
|
||||
val processor = XmlProcessor(pathData.source, BlameLogger(StdLogger(VERBOSE)))
|
||||
processor.process(resFile, input.inputStream().buffered())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.itsaky.androidide.views.ContentTranslatingDrawerLayout 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"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:id="@+id/editor_drawerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
tools:ignore="DuplicateIncludedIds">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/realContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<aapt:attr name="android:background">
|
||||
<shape><solid android:color="#ffffff"/></shape>
|
||||
</aapt:attr>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/editor_appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/editor_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="10dp" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/build_progress_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="@color/secondaryColor" />
|
||||
|
||||
<com.itsaky.androidide.views.MaterialBanner
|
||||
android:id="@+id/syncBanner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:tabGravity="start"
|
||||
style="@style/AppTheme.TabLayout"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/content_background"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
<!--Views in this flipper are dynamically added and must be instances of CodeEditorView-->
|
||||
<ViewFlipper
|
||||
android:id="@+id/editor_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/editor_sheet_peek_height"
|
||||
android:background="@color/content_background" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_editor_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/secondaryTextColor"
|
||||
android:textSize="30sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_editor_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:textColor="@color/secondaryTextColor_light"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="@id/no_editor_title"
|
||||
app:layout_constraintStart_toStartOf="@id/no_editor_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/no_editor_title"
|
||||
tools:text="Open right drawer for files\nSwipe from bottom for build output" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ViewFlipper>
|
||||
|
||||
<com.itsaky.androidide.views.EditorBottomSheet
|
||||
android:id="@+id/bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:behavior_hideable="false"
|
||||
app:behavior_peekHeight="@dimen/editor_sheet_peek_height"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
|
||||
|
||||
<include
|
||||
android:id="@+id/diagnosticInfo"
|
||||
layout="@layout/layout_diagnostic_info" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/startNav"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:headerLayout="@layout/layout_nav_header"
|
||||
app:itemIconTint="@color/primaryIconColor"
|
||||
app:itemTextColor="@color/primaryTextColor"
|
||||
app:menu="@menu/menu_editor_nav" />
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/endNav"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<!-- When changing android:tag here, also change it in FileTreeFragment.kt -->
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/file_tree"
|
||||
android:name="com.itsaky.androidide.fragments.FileTreeFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:tag="editor.fileTree" />
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
|
||||
</com.itsaky.androidide.views.ContentTranslatingDrawerLayout>
|
||||
+14
-11
@@ -14,18 +14,21 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.values.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
package com.itsaky.androidide.utils
|
||||
|
||||
/**
|
||||
* Represents a dimension value.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionResource extends AbstractResource {
|
||||
import android.content.Context
|
||||
import android.content.res.Resources.Theme
|
||||
import android.util.TypedValue
|
||||
|
||||
public DimensionResource(@NonNull String name, @NonNull String value) {
|
||||
super(name, value);
|
||||
}
|
||||
@JvmOverloads
|
||||
fun Context.resolveAttr(id: Int, resolveRefs: Boolean = true): Int {
|
||||
return theme.resolveAttr(id)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun Theme.resolveAttr(id: Int, resolveRefs: Boolean = true): Int =
|
||||
TypedValue().let {
|
||||
resolveAttribute(id, it, resolveRefs)
|
||||
it.data
|
||||
}
|
||||
@@ -69,7 +69,7 @@ dependencies {
|
||||
api project (':lsp:models')
|
||||
api project (':preferences')
|
||||
|
||||
compileOnly project (':xml-inflater')
|
||||
compileOnly project (':sdk-info')
|
||||
compileOnly project (':actions')
|
||||
compileOnly project (':common')
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.itsaky.androidide.lsp.xml.providers.CodeFormatProvider;
|
||||
import com.itsaky.androidide.lsp.xml.providers.XmlCompletionProvider;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.api.Project;
|
||||
import com.itsaky.sdk.SDKInfo;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -59,12 +58,11 @@ public class XMLLanguageServer implements ILanguageServer {
|
||||
public static final String SERVER_ID = "xml";
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
|
||||
private ILanguageClient client;
|
||||
|
||||
private IServerSettings settings;
|
||||
|
||||
public XMLLanguageServer() {}
|
||||
|
||||
|
||||
@Override
|
||||
public String getServerId() {
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.apiinfo;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.apiinfo.models.ClassInfo;
|
||||
import com.itsaky.apiinfo.models.FieldInfo;
|
||||
import com.itsaky.apiinfo.models.MethodInfo;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ApiInfo {
|
||||
|
||||
public static final String NAME = "name";
|
||||
public static final String SINCE = "since";
|
||||
public static final String DEPRECATED = "deprecated";
|
||||
public static final String REMOVED = "removed";
|
||||
public static final String EXTENDS = "extends";
|
||||
public static final String IMPLEMENTS = "implements";
|
||||
public static final String FIELD = "field";
|
||||
public static final String METHOD = "method";
|
||||
private final Map<String, ClassInfo> classInfos;
|
||||
private boolean read = false;
|
||||
|
||||
public ApiInfo(@NonNull final Resources resources) throws Exception {
|
||||
this.classInfos = new HashMap<>();
|
||||
doRead(resources);
|
||||
}
|
||||
|
||||
public void doRead(Resources resources) throws Exception {
|
||||
final InputStream in = resources.openRawResource(R.raw.versions_xml);
|
||||
if (in == null) {
|
||||
throw new Resources.NotFoundException("Cannot find versions.xml");
|
||||
}
|
||||
Document doc = Jsoup.parse(readStream(in));
|
||||
Elements classes = doc.getElementsByTag("api").first().getElementsByTag("class");
|
||||
if (classes.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Element clazz : classes) {
|
||||
ClassInfo info = parseClassInfo(clazz);
|
||||
if (info != null) classInfos.put(info.name, info);
|
||||
}
|
||||
|
||||
read = true;
|
||||
}
|
||||
|
||||
private String readStream(InputStream in) throws IOException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line.trim());
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.substring(sb.indexOf("<api"));
|
||||
}
|
||||
|
||||
private ClassInfo parseClassInfo(Element clazz) {
|
||||
if (!clazz.hasAttr(NAME)) return null;
|
||||
ClassInfo info = new ClassInfo();
|
||||
|
||||
info.name = clazz.attr(NAME).replace("/", ".").replace("$", ".");
|
||||
|
||||
if (clazz.hasAttr(SINCE)) {
|
||||
info.since = Integer.parseInt(clazz.attr(SINCE));
|
||||
}
|
||||
|
||||
if (clazz.hasAttr(DEPRECATED)) {
|
||||
info.deprecated = Integer.parseInt(clazz.attr(DEPRECATED));
|
||||
}
|
||||
|
||||
if (clazz.hasAttr(REMOVED)) {
|
||||
info.removed = Integer.parseInt(clazz.attr(REMOVED));
|
||||
}
|
||||
|
||||
Elements superClass = clazz.getElementsByTag(EXTENDS);
|
||||
if (superClass.size() == 1) {
|
||||
info.superClass = superClass.first().attr(NAME);
|
||||
}
|
||||
|
||||
Elements interfaces = clazz.getElementsByTag(IMPLEMENTS);
|
||||
if (interfaces.size() > 0) {
|
||||
for (Element iface : interfaces) {
|
||||
info.interfaces.add(iface.attr(NAME));
|
||||
}
|
||||
}
|
||||
|
||||
Elements fields = clazz.getElementsByTag(FIELD);
|
||||
if (fields.size() > 0) {
|
||||
for (Element field : fields) {
|
||||
putField(field, info);
|
||||
}
|
||||
}
|
||||
|
||||
Elements methods = clazz.getElementsByTag(METHOD);
|
||||
if (methods.size() > 0) {
|
||||
for (Element method : methods) {
|
||||
putMethod(method, info);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private void putField(Element field, ClassInfo clazz) {
|
||||
if (!field.hasAttr(NAME)) return;
|
||||
FieldInfo info = new FieldInfo();
|
||||
|
||||
info.name = field.attr(NAME);
|
||||
|
||||
if (field.hasAttr(SINCE)) {
|
||||
info.since = Integer.parseInt(field.attr(SINCE));
|
||||
}
|
||||
|
||||
if (field.hasAttr(DEPRECATED)) {
|
||||
info.deprecated = Integer.parseInt(field.attr(DEPRECATED));
|
||||
}
|
||||
|
||||
if (field.hasAttr(REMOVED)) {
|
||||
info.removed = Integer.parseInt(field.attr(REMOVED));
|
||||
}
|
||||
|
||||
clazz.fields.put(info.name, info);
|
||||
}
|
||||
|
||||
private void putMethod(Element method, ClassInfo clazz) {
|
||||
if (!method.hasAttr(NAME)) return;
|
||||
MethodInfo info = new MethodInfo();
|
||||
|
||||
info.name = method.attr(NAME);
|
||||
info.simpleName = info.name.substring(0, info.name.indexOf("("));
|
||||
|
||||
if (method.hasAttr(SINCE)) {
|
||||
info.since = Integer.parseInt(method.attr(SINCE));
|
||||
}
|
||||
|
||||
if (method.hasAttr(DEPRECATED)) {
|
||||
info.deprecated = Integer.parseInt(method.attr(DEPRECATED));
|
||||
}
|
||||
|
||||
if (method.hasAttr(REMOVED)) {
|
||||
info.removed = Integer.parseInt(method.attr(REMOVED));
|
||||
}
|
||||
|
||||
clazz.methods.put(info.name, info);
|
||||
}
|
||||
|
||||
public boolean hasRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
public ClassInfo getClassByName(String qualifiedName) {
|
||||
return classInfos.get(qualifiedName);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.apiinfo.models;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.eclipse.jdt.core.Signature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Holds information about a class */
|
||||
public class ClassInfo extends Info {
|
||||
|
||||
/** Interfaces that this class implements */
|
||||
public final List<String> interfaces = new ArrayList<>();
|
||||
/** Fields of this class */
|
||||
public final Map<String, FieldInfo> fields = new HashMap<>();
|
||||
/** Methods of this class */
|
||||
public final Map<String, MethodInfo> methods = new HashMap<>();
|
||||
/** Superclass of this class */
|
||||
public String superClass;
|
||||
|
||||
/**
|
||||
* Get the field by its simple name
|
||||
*
|
||||
* @param name Name of the field to find
|
||||
*/
|
||||
public FieldInfo getFieldByName(String name) {
|
||||
return fields.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method by its name and parameter types
|
||||
*
|
||||
* @param name Name of the method to find
|
||||
* @param parameters Parameter types of the method
|
||||
*/
|
||||
public MethodInfo getMethod(String name, String[] parameters) {
|
||||
|
||||
// Convert parameter types to their binary version
|
||||
final String[] parameterTypes = new String[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
String paramType = parameters[i];
|
||||
String binaryParamType = Signature.createTypeSignature(paramType, true /* isResolved */);
|
||||
parameterTypes[i] = binaryParamType;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, MethodInfo> entry : methods.entrySet()) {
|
||||
MethodInfo method = entry.getValue();
|
||||
|
||||
// Check if this method's simple name is what we are looking for
|
||||
if (method != null && method.simpleName.trim().equals(name.trim())) {
|
||||
|
||||
// Get parameter types of this method
|
||||
// Takes binary representation, returns binary representation
|
||||
String[] paramTypes = Signature.getParameterTypes(method.name);
|
||||
|
||||
// Join the parameters to check if they are equal This is just more efficient
|
||||
final String params1 = TextUtils.join(",", paramTypes);
|
||||
final String params2 = TextUtils.join(",", parameterTypes);
|
||||
|
||||
// We found what we were looking for!
|
||||
if (params1.equals(params2)) return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.apiinfo.models;
|
||||
|
||||
/**
|
||||
* Holds information about a field
|
||||
*
|
||||
* <p>No difference as compared to Info
|
||||
*/
|
||||
public class FieldInfo extends Info {}
|
||||
@@ -1,36 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.apiinfo.models;
|
||||
|
||||
/** Base class for information about a class, method, field */
|
||||
public abstract class Info {
|
||||
|
||||
/** Name of this class, method or field */
|
||||
public String name = "";
|
||||
|
||||
/** API level in which this info was deprecated */
|
||||
public int deprecated = -1;
|
||||
|
||||
/** API level in which this was added */
|
||||
public int since = -1;
|
||||
|
||||
/** API level in which this was removed */
|
||||
public int removed = -1;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.apiinfo.models;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Holds information about a method */
|
||||
public class MethodInfo extends Info {
|
||||
|
||||
/** Parameter types of this method */
|
||||
public final List<String> parameters = new ArrayList<>();
|
||||
/**
|
||||
* In case of a method, the {@link com.itsaky.apiinfo.models.Info#name} contains signature of the
|
||||
* method This field contains the actual simple name
|
||||
*/
|
||||
public String simpleName;
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.attrinfo;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.attrinfo.models.Styleable;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.parser.Parser;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* A parser for parsing <b>attrs.xml</b>. This parser maps the attributes with the declared
|
||||
* styleables.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrInfo {
|
||||
|
||||
/** Styles mapped by names; */
|
||||
public final Map<String, Styleable> styles = new HashMap<>();
|
||||
|
||||
public final Map<String, Attr> attributes = new TreeMap<>();
|
||||
public final Styleable NO_PARENT;
|
||||
|
||||
public AttrInfo(@NonNull final Resources resources) throws Exception {
|
||||
NO_PARENT = new Styleable("<unknown_parent>");
|
||||
|
||||
Objects.requireNonNull(resources, "Cannot read from null resources.");
|
||||
|
||||
final var in = resources.openRawResource(R.raw.attrs_xml);
|
||||
parseFromStream(in);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Attr getAttribute(@NonNull final String name) {
|
||||
var attr = this.attributes.get(name);
|
||||
if (attr == null) {
|
||||
final var a = NO_PARENT.attributes.stream().filter(aa -> aa.name.equals(name)).findFirst();
|
||||
attr = a.orElse(null);
|
||||
}
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, Attr> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Styleable getStyle(@NonNull final String name) {
|
||||
return this.styles.get(name);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, Styleable> getStyles() {
|
||||
return styles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(NO_PARENT, styles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof AttrInfo)) {
|
||||
return false;
|
||||
}
|
||||
AttrInfo that = (AttrInfo) o;
|
||||
return Objects.equals(NO_PARENT, that.NO_PARENT) && Objects.equals(styles, that.styles);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AttrInfoCompat{" + "NO_PARENT=" + NO_PARENT + ", styles=" + styles + '}';
|
||||
}
|
||||
|
||||
protected void parseFromStream(InputStream in) throws Exception {
|
||||
Objects.requireNonNull(in, "Cannot read from null input stream");
|
||||
|
||||
Document doc = Jsoup.parse(in, null, "", Parser.xmlParser());
|
||||
final var resources = doc.getElementsByTag("resources").first();
|
||||
|
||||
parseFromResources(resources);
|
||||
}
|
||||
|
||||
protected void parseFromResources(Element resources) {
|
||||
Objects.requireNonNull(resources, "Cannot parse from null resource element");
|
||||
|
||||
this.styles.clear();
|
||||
|
||||
for (int i = 0; i < resources.childrenSize(); i++) {
|
||||
final var child = resources.child(i);
|
||||
if (child.tagName().equals("declare-styleable")) {
|
||||
this.styles.put(parseStyleable(child).name, parseStyleable(child));
|
||||
} else if (child.tagName().equals("attr")) {
|
||||
NO_PARENT.attributes.add(parseAttr(child));
|
||||
}
|
||||
}
|
||||
|
||||
this.styles.put(NO_PARENT.name, NO_PARENT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Styleable parseStyleable(@NonNull final Element styleable) {
|
||||
checkName(styleable);
|
||||
|
||||
final var name = styleable.attr("name");
|
||||
final var style = new Styleable(name);
|
||||
|
||||
for (int i = 0; i < styleable.childrenSize(); i++) {
|
||||
final var attr = styleable.child(i);
|
||||
if (!attr.tagName().equals("attr")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
style.attributes.add(parseAttr(attr));
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Attr parseAttr(@NonNull final Element attr) {
|
||||
checkName(attr);
|
||||
|
||||
final var attribute = new Attr(attr.attr("name"));
|
||||
if (attribute.name.contains(":")) {
|
||||
final var split = attribute.name.split(":", 2);
|
||||
attribute.namespace = INamespace.ANDROID;
|
||||
attribute.name = split[1];
|
||||
}
|
||||
|
||||
if (attribute.name.equals("gravity")) {
|
||||
System.out.println("This is it");
|
||||
}
|
||||
|
||||
if (attr.hasAttr("format")) {
|
||||
attribute.format = Attr.formatForName(attr.attr("format"));
|
||||
if (attribute.hasFormat(Attr.BOOLEAN)) {
|
||||
attribute.possibleValues.add("true");
|
||||
attribute.possibleValues.add("false");
|
||||
}
|
||||
}
|
||||
|
||||
final var enums = attr.getElementsByTag("enum");
|
||||
final var flags = attr.getElementsByTag("flag");
|
||||
|
||||
if (enums.size() > 0) {
|
||||
for (var enumEntry : enums) {
|
||||
checkName(enumEntry);
|
||||
attribute.possibleValues.add(enumEntry.attr("name"));
|
||||
}
|
||||
|
||||
if (!attribute.hasFormat(Attr.ENUM)) {
|
||||
attribute.format |= Attr.ENUM;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.size() > 0) {
|
||||
for (var flagEntry : flags) {
|
||||
checkName(flagEntry);
|
||||
attribute.possibleValues.add(flagEntry.attr("name"));
|
||||
}
|
||||
|
||||
if (!attribute.hasFormat(Attr.FLAG)) {
|
||||
attribute.format |= Attr.FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.attributes.containsKey(attribute.name)) {
|
||||
final var present = this.attributes.get(attribute.name);
|
||||
if (present != null) {
|
||||
if (present.hasPossibleValues()) {
|
||||
attribute.possibleValues.addAll(present.possibleValues);
|
||||
}
|
||||
|
||||
if (attribute.format == 0 && present.format != 0) {
|
||||
attribute.format = present.format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.attributes.put(attribute.name, attribute);
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
private void checkName(@NonNull Element child) {
|
||||
if (!child.hasAttr("name")) {
|
||||
throw new IllegalStateException("Element does not have 'name' attribute: " + child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.attrinfo.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents an xml attribute which is read from {@code attrs.xml}.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
* @see com.itsaky.attrinfo.AttrInfo
|
||||
*/
|
||||
public class Attr implements Parcelable {
|
||||
|
||||
public static final int REFERENCE = 1;
|
||||
public static final int COLOR = 1 << 1;
|
||||
public static final int BOOLEAN = 1 << 2;
|
||||
public static final int DIMENSION = 1 << 3;
|
||||
public static final int FLOAT = 1 << 4;
|
||||
public static final int INTEGER = 1 << 5;
|
||||
public static final int FRACTION = 1 << 6;
|
||||
public static final int STRING = 1 << 7;
|
||||
public static final int ENUM = 1 << 8;
|
||||
public static final int FLAG = 1 << 9;
|
||||
public static final int UNKNOWN = 1 << 10;
|
||||
|
||||
public static final String FORMAT_REFERENCE = "reference";
|
||||
public static final String FORMAT_COLOR = "color";
|
||||
public static final String FORMAT_BOOLEAN = "boolean";
|
||||
public static final String FORMAT_DIMENSION = "dimension";
|
||||
public static final String FORMAT_FLOAT = "float";
|
||||
public static final String FORMAT_INTEGER = "integer";
|
||||
public static final String FORMAT_FRACTION = "fraction";
|
||||
public static final String FORMAT_STRING = "string";
|
||||
public static final String FORMAT_ENUM = "enum";
|
||||
public static final String FORMAT_FLAG = "flag";
|
||||
public static final Creator<Attr> CREATOR =
|
||||
new Creator<>() {
|
||||
@Override
|
||||
public Attr createFromParcel(Parcel in) {
|
||||
return new Attr(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attr[] newArray(int size) {
|
||||
return new Attr[size];
|
||||
}
|
||||
};
|
||||
private static final ILogger LOG = ILogger.newInstance("AttrInfo::Attr");
|
||||
public INamespace namespace;
|
||||
public String name;
|
||||
public Set<String> possibleValues;
|
||||
public int format;
|
||||
|
||||
public Attr(String name) {
|
||||
this(name, INamespace.ANDROID);
|
||||
}
|
||||
|
||||
public Attr(String name, INamespace namespace) {
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
this.possibleValues = new TreeSet<>();
|
||||
}
|
||||
|
||||
protected Attr(@NonNull Parcel in) {
|
||||
this.namespace = in.readParcelable(INamespace.class.getClassLoader());
|
||||
this.name = in.readString();
|
||||
this.format = in.readInt();
|
||||
|
||||
final var list = new ArrayList<String>();
|
||||
in.readStringList(list);
|
||||
this.possibleValues = new TreeSet<>(list);
|
||||
}
|
||||
|
||||
public static int formatForName(@NonNull String names) {
|
||||
var result = 0;
|
||||
if (names.contains("|")) {
|
||||
for (var name : names.split(Pattern.quote("|"))) {
|
||||
result |= formatForSingleName(name);
|
||||
}
|
||||
} else {
|
||||
result = formatForSingleName(names);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public static int formatForSingleName(@NonNull String name) {
|
||||
switch (name) {
|
||||
case FORMAT_REFERENCE:
|
||||
return REFERENCE;
|
||||
case FORMAT_COLOR:
|
||||
return COLOR;
|
||||
case FORMAT_BOOLEAN:
|
||||
return BOOLEAN;
|
||||
case FORMAT_DIMENSION:
|
||||
return DIMENSION;
|
||||
case FORMAT_FLOAT:
|
||||
return FLOAT;
|
||||
case FORMAT_INTEGER:
|
||||
return INTEGER;
|
||||
case FORMAT_FRACTION:
|
||||
return FRACTION;
|
||||
case FORMAT_STRING:
|
||||
return STRING;
|
||||
case FORMAT_ENUM:
|
||||
return ENUM;
|
||||
case FORMAT_FLAG:
|
||||
return FLAG;
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static String createFormatText(int format) {
|
||||
final var formats = new ArrayList<String>();
|
||||
if ((format & REFERENCE) != 0) {
|
||||
formats.add(FORMAT_REFERENCE);
|
||||
}
|
||||
|
||||
if ((format & COLOR) != 0) {
|
||||
formats.add(FORMAT_COLOR);
|
||||
}
|
||||
|
||||
if ((format & BOOLEAN) != 0) {
|
||||
formats.add(FORMAT_BOOLEAN);
|
||||
}
|
||||
|
||||
if ((format & DIMENSION) != 0) {
|
||||
formats.add(FORMAT_DIMENSION);
|
||||
}
|
||||
|
||||
if ((format & FLOAT) != 0) {
|
||||
formats.add(FORMAT_FLOAT);
|
||||
}
|
||||
|
||||
if ((format & INTEGER) != 0) {
|
||||
formats.add(FORMAT_INTEGER);
|
||||
}
|
||||
|
||||
if ((format & FRACTION) != 0) {
|
||||
formats.add(FORMAT_FRACTION);
|
||||
}
|
||||
|
||||
if ((format & STRING) != 0) {
|
||||
formats.add(FORMAT_STRING);
|
||||
}
|
||||
|
||||
if ((format & ENUM) != 0) {
|
||||
formats.add(FORMAT_ENUM);
|
||||
}
|
||||
|
||||
if ((format & FLAG) != 0) {
|
||||
formats.add(FORMAT_FLAG);
|
||||
}
|
||||
|
||||
if (formats.isEmpty()) {
|
||||
formats.add("unknown");
|
||||
}
|
||||
|
||||
return TextUtils.join("|", formats);
|
||||
}
|
||||
|
||||
public boolean hasPossibleValues() {
|
||||
return possibleValues != null && possibleValues.size() > 0;
|
||||
}
|
||||
|
||||
public boolean hasFormat(int format) {
|
||||
return (this.format & format) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(namespace, name, possibleValues, format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Attr attr = (Attr) o;
|
||||
return format == attr.format
|
||||
&& Objects.equals(namespace, attr.namespace)
|
||||
&& Objects.equals(name, attr.name)
|
||||
&& Objects.equals(possibleValues, attr.possibleValues);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Attr ["
|
||||
+ " name: "
|
||||
+ name
|
||||
+ "\n"
|
||||
+ " namespace: "
|
||||
+ namespace
|
||||
+ "\n"
|
||||
+ " values: "
|
||||
+ TextUtils.join(", ", this.possibleValues)
|
||||
+ "\n"
|
||||
+ " format: "
|
||||
+ format
|
||||
+ "\n"
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(namespace, flags);
|
||||
dest.writeString(name);
|
||||
dest.writeInt(format);
|
||||
dest.writeStringList(new ArrayList<>(this.possibleValues));
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.attrinfo.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A <code><declare-styleable></code>.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class Styleable {
|
||||
|
||||
public final String name;
|
||||
public final Set<Attr> attributes = new HashSet<>();
|
||||
|
||||
public Styleable(String name) {
|
||||
if (name == null || name.trim().length() <= 0) {
|
||||
throw new IllegalArgumentException("Invalid name specified for declared styleable");
|
||||
}
|
||||
|
||||
this.name = name.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Styleable)) {
|
||||
return false;
|
||||
}
|
||||
Styleable styleable = (Styleable) o;
|
||||
return Objects.equals(name, styleable.name) && Objects.equals(attributes, styleable.attributes);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Styleable{" + "name='" + name + '\'' + ", attributes=" + attributes + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class that holds all of the 'sdk-info' APIs.
|
||||
*
|
||||
* @see ApiInfo
|
||||
* @see AttrInfo
|
||||
* @see WidgetInfo
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class SDKInfo {
|
||||
|
||||
private final AttrInfo attrInfo;
|
||||
private final ApiInfo apiInfo;
|
||||
private final WidgetInfo widgetInfo;
|
||||
|
||||
public SDKInfo(@NonNull final Context context) throws Exception {
|
||||
this(context.getResources());
|
||||
}
|
||||
|
||||
public SDKInfo(@NonNull final Resources resources) throws Exception {
|
||||
Objects.requireNonNull(resources);
|
||||
this.apiInfo = new ApiInfo(resources);
|
||||
this.attrInfo = new AttrInfo(resources);
|
||||
this.widgetInfo = new WidgetInfo(resources);
|
||||
}
|
||||
|
||||
public AttrInfo getAttrInfo() {
|
||||
return attrInfo;
|
||||
}
|
||||
|
||||
public ApiInfo getApiInfo() {
|
||||
return apiInfo;
|
||||
}
|
||||
|
||||
public WidgetInfo getWidgetInfo() {
|
||||
return widgetInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.widgets;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.widgets.models.Widget;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collection;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class WidgetInfo {
|
||||
|
||||
private final SortedMap<String, Widget> widgets;
|
||||
private final SortedMap<String, String[]> params;
|
||||
|
||||
public WidgetInfo(@NonNull final Resources resources) throws IOException {
|
||||
this.widgets = new TreeMap<>();
|
||||
this.params = new TreeMap<>();
|
||||
readWidgets(resources);
|
||||
}
|
||||
|
||||
private void readWidgets(@NonNull final Resources resources) throws IOException {
|
||||
final InputStream in = resources.openRawResource(com.itsaky.androidide.resources.R.raw.widgets);
|
||||
if (in == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
final var split = line.split("\\s");
|
||||
final var name = split[0];
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var code = name.charAt(0);
|
||||
|
||||
final var entryName = name.substring(1);
|
||||
final var isLayout = code == 'L'; // L -> Layout, W -> Widget, P -> LayoutParam
|
||||
final var superclasses = new String[split.length - 1];
|
||||
|
||||
System.arraycopy(split, 1, superclasses, 0, superclasses.length);
|
||||
|
||||
if (code == 'P') {
|
||||
final var className = entryName.substring(0, entryName.lastIndexOf("."));
|
||||
this.params.put(className, superclasses);
|
||||
} else {
|
||||
final var widget = new Widget(entryName, isLayout);
|
||||
widget.superclasses = superclasses;
|
||||
widgets.put(entryName, widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Widget getWidget(String name) {
|
||||
return this.widgets.getOrDefault(name, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Widget getWidgetBySimpleName(@NonNull final String name) {
|
||||
for (final var entry : this.widgets.entrySet()) {
|
||||
final var key = entry.getKey();
|
||||
final var simple = key.substring(key.lastIndexOf(".") + 1);
|
||||
if (simple.equals(name)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<Widget> getWidgets() {
|
||||
return this.widgets.values();
|
||||
}
|
||||
|
||||
public String[] getLayoutParamSuperClasses(final String className) {
|
||||
return this.params.get(className);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.widgets.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class Widget implements Comparable<Widget> {
|
||||
|
||||
public String name;
|
||||
public String simpleName;
|
||||
public boolean isViewGroup;
|
||||
|
||||
public String[] superclasses = new String[0];
|
||||
|
||||
public Widget(@NonNull String name, boolean isViewGroup) {
|
||||
this.name = name;
|
||||
this.simpleName = name.substring(name.lastIndexOf(".") + 1);
|
||||
this.isViewGroup = isViewGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Widget that) {
|
||||
if (that != null) {
|
||||
return this.simpleName.compareTo(that.simpleName);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ include ':lookup'
|
||||
include ':sdk-info'
|
||||
include ':shared'
|
||||
include ':treeview'
|
||||
include ':xml-inflater'
|
||||
|
||||
include ':gradle-plugin'
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,85 +0,0 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
/bin/
|
||||
/gen/
|
||||
/out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
/build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
@@ -1,44 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace "com.itsaky.inflater"
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree (dir: 'libs', include: ['*.jar'])
|
||||
implementation androidx.ktx
|
||||
implementation androidx.annotation
|
||||
implementation androidx.appcompat
|
||||
|
||||
implementation google.guava
|
||||
implementation common.utilcode
|
||||
implementation common.jsoup
|
||||
implementation common.kotlin
|
||||
|
||||
implementation project (":common")
|
||||
implementation project (":resources")
|
||||
api project (':sdk-info')
|
||||
}
|
||||
Vendored
-17
@@ -1,17 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/bogdan/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,117 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
/**
|
||||
* Value type of {@link IAttribute}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public enum AttrType {
|
||||
|
||||
/** Refers to another resource */
|
||||
REFERENCE("reference"),
|
||||
|
||||
/** Color value */
|
||||
COLOR("color"),
|
||||
|
||||
/** Either {@code true} or {@code false} */
|
||||
BOOLEAN("boolean"),
|
||||
|
||||
/** A dimension like 10dp, 14sp etc. */
|
||||
DIMENSION("dimension"),
|
||||
|
||||
/** A floating point number. E.g.: 1.9, 123.45 */
|
||||
FLOAT("float"),
|
||||
|
||||
/** An integer value */
|
||||
INTEGER("integer"),
|
||||
|
||||
/** A string value */
|
||||
STRING("string"),
|
||||
|
||||
/** A fraction */
|
||||
FRACTION("fraction"),
|
||||
|
||||
/** A enum value. Unlike {@link #FLAG}, an attribute can have only one of this. */
|
||||
ENUM("enum"),
|
||||
|
||||
/** A flag value. May contain on or more values */
|
||||
FLAG("flag"),
|
||||
|
||||
/** An unknown value. The layout inflater does not consider this */
|
||||
UNKNOWN("");
|
||||
|
||||
public static final String FORMAT_REF = REFERENCE.getFormat();
|
||||
public static final String FORMAT_COLOR = COLOR.getFormat();
|
||||
public static final String FORMAT_BOOL = BOOLEAN.getFormat();
|
||||
public static final String FORMAT_DIMEN = DIMENSION.getFormat();
|
||||
public static final String FORMAT_FLOAT = FLOAT.getFormat();
|
||||
public static final String FORMAT_INT = INTEGER.getFormat();
|
||||
public static final String FORMAT_STRING = STRING.getFormat();
|
||||
public static final String FORMAT_FRACTION = FRACTION.getFormat();
|
||||
public static final String FORMAT_ENUM = ENUM.getFormat();
|
||||
public static final String FORMAT_FLAG = FLAG.getFormat();
|
||||
private final String format;
|
||||
|
||||
private AttrType(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute type from format
|
||||
*
|
||||
* @param format Format, as declared in styleables.
|
||||
* @return The Attribute type
|
||||
*/
|
||||
public static AttrType fromFormat(String format) {
|
||||
if (FORMAT_REF.equals(format)) {
|
||||
return AttrType.REFERENCE;
|
||||
} else if (FORMAT_COLOR.equals(format)) {
|
||||
return AttrType.COLOR;
|
||||
} else if (FORMAT_BOOL.equals(format)) {
|
||||
return AttrType.BOOLEAN;
|
||||
} else if (FORMAT_DIMEN.equals(format)) {
|
||||
return AttrType.DIMENSION;
|
||||
} else if (FORMAT_FLOAT.equals(format)) {
|
||||
return AttrType.FLOAT;
|
||||
} else if (FORMAT_INT.equals(format)) {
|
||||
return AttrType.INTEGER;
|
||||
} else if (FORMAT_STRING.equals(format)) {
|
||||
return AttrType.STRING;
|
||||
} else if (FORMAT_FRACTION.equals(format)) {
|
||||
return AttrType.FRACTION;
|
||||
} else if (FORMAT_ENUM.equals(format)) {
|
||||
return AttrType.ENUM;
|
||||
} else if (FORMAT_FLAG.equals(format)) {
|
||||
return AttrType.FLAG;
|
||||
}
|
||||
|
||||
return AttrType.UNKNOWN;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return this.format;
|
||||
}
|
||||
|
||||
public boolean hasFixedValues() {
|
||||
return this == AttrType.ENUM || this == AttrType.FLAG;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseLayoutInflater extends ILayoutInflater {
|
||||
|
||||
private final Set<IInflateListener> inflateListeners = new HashSet<>();
|
||||
protected boolean notify = true;
|
||||
|
||||
@Override
|
||||
public void registerInflateListener(IInflateListener listener) {
|
||||
inflateListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener(IInflateListener listener) {
|
||||
inflateListeners.remove(listener);
|
||||
}
|
||||
|
||||
protected void preInflate() {
|
||||
if (notify) {
|
||||
for (IInflateListener listener : inflateListeners) {
|
||||
listener.onBeginInflate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void postApplyAttribute(IAttribute attr, IView view) {
|
||||
if (notify) {
|
||||
for (IInflateListener listener : inflateListeners) {
|
||||
listener.onApplyAttribute(attr, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void postCreateView(IView view) {
|
||||
if (notify) {
|
||||
for (IInflateListener listener : inflateListeners) {
|
||||
listener.onInflateView(view, view.getParent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void postInflate(IView root) {
|
||||
if (notify) {
|
||||
for (IInflateListener listener : inflateListeners) {
|
||||
listener.onFinishInflate(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Set<IInflateListener> getInflateListeners() {
|
||||
return this.inflateListeners;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Attribute that can be applied to a {@link IView}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IAttribute {
|
||||
|
||||
/**
|
||||
* Returned by {@link #compareNull(Object, Object)} to indicate that the comparison must be done
|
||||
* further.
|
||||
*/
|
||||
int DO_COMPARE = 2;
|
||||
|
||||
Comparator<IAttribute> COMPARATOR =
|
||||
(first, second) -> {
|
||||
var result = compareNull(first, second);
|
||||
if (result != IAttribute.DO_COMPARE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = compareNull(first.getNamespace(), second.getNamespace());
|
||||
if (result != IAttribute.DO_COMPARE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we reach here, then it means that the attributes and their namespaces are not
|
||||
// null
|
||||
// First, the attributes must be compared by their namespaces
|
||||
|
||||
final var firstNs = first.getNamespace();
|
||||
final var secondNs = second.getNamespace();
|
||||
|
||||
if (firstNs != null && firstNs.equals(secondNs)) {
|
||||
// Compare by attribute names if the namespaces are same
|
||||
return first.getAttributeName().compareTo(second.getAttributeName());
|
||||
}
|
||||
|
||||
// xmlns declarations must be on top
|
||||
if (firstNs != null && firstNs.equals(INamespace.DECLARATOR)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (secondNs != null && secondNs.equals(INamespace.DECLARATOR)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
private static int compareNull(Object first, Object second) {
|
||||
if (first == null && second == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (first != null && second == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (first == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return DO_COMPARE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
INamespace getNamespace();
|
||||
|
||||
@NonNull
|
||||
String getAttributeName();
|
||||
|
||||
@NonNull
|
||||
String getValue();
|
||||
|
||||
/**
|
||||
* Set the given string as a value to this attribute. This will set {@link #isApplied()} to return
|
||||
* {@code true}.
|
||||
*
|
||||
* @param value The value to set.
|
||||
*/
|
||||
void apply(String value);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/** An adapter that handles attributes of a specific type of view */
|
||||
public interface IAttributeAdapter {
|
||||
|
||||
/**
|
||||
* Set the resource finder used by this class.
|
||||
*
|
||||
* @param resourceFinder The resource finder.
|
||||
*/
|
||||
void setResourceFinder(IResourceTable resourceFinder);
|
||||
|
||||
/**
|
||||
* Can this adapter handle attributes of the provided view?
|
||||
*
|
||||
* @param view The view to which the attributes will be applied
|
||||
* @return {@code true} if this adapter can handle attributes of the specified view
|
||||
*/
|
||||
boolean isApplicableTo(View view);
|
||||
|
||||
/**
|
||||
* Apply the provided attribute to the view.
|
||||
*
|
||||
* <p>This will be called if and only if {@link #isApplicableTo(View)} returns {@code true}.
|
||||
*
|
||||
* @param attribute The attribute to apply
|
||||
* @param view The view to apply attribute to
|
||||
* @return {@code true} if the attribute was applied successfully {@code false} otherwise.
|
||||
*/
|
||||
boolean apply(IAttribute attribute, View view);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds references to view IDs. The {@link #newRound()} must be called before inflating a layout
|
||||
* from the XML. This will make sure that we cleanup the existing IDs.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class IDTable {
|
||||
|
||||
/**
|
||||
* Map<String, Integer> containing the IDs. Entries are mapped by the name of the ID.
|
||||
*
|
||||
* <p>For example : if android:id="@+id/my_id" is provided for view, then an entry will be created
|
||||
* in this map with the key : 'my_id' and a random integer as the value.
|
||||
*
|
||||
* <p>No entries are created for views which do not have the android:id attribute.
|
||||
*/
|
||||
private static final Map<String, Integer> ids = new HashMap<>();
|
||||
|
||||
/** Clears the existing ID map. */
|
||||
public static void newRound() {
|
||||
ids.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ID entry for the given name.
|
||||
*
|
||||
* @param idName The name of the ID.
|
||||
* @return The assigned ID.
|
||||
*/
|
||||
public static int newId(String idName) {
|
||||
|
||||
{
|
||||
// In some cases, @+id might be used as a reference
|
||||
// to another ID. So we check if an ID with the same
|
||||
// name is already present or not.
|
||||
final int id = getId(idName);
|
||||
if (id != View.NO_ID) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
final int id = View.generateViewId();
|
||||
ids.put(idName, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID mapped by the given name.
|
||||
*
|
||||
* @param name The name of the ID.
|
||||
* @return The mapped ID or {@link android.view.View#NO_ID}
|
||||
*/
|
||||
public static int getId(String name) {
|
||||
final Integer id = ids.getOrDefault(name, null);
|
||||
if (id == null) {
|
||||
return View.NO_ID;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
/**
|
||||
* A fixed value which represents an enum of a flag
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IFixedValue {
|
||||
|
||||
/**
|
||||
* Is this value a flag? An attribute which accepts a flag may be combined with other flags.
|
||||
*
|
||||
* @return {@code true} if this value is a flag. {@code false} otherwise.
|
||||
*/
|
||||
boolean isFlag();
|
||||
|
||||
/**
|
||||
* The name of this enum/flag as represented in an XML file
|
||||
*
|
||||
* @return The name of this flag
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Get the value of this enum/flag. This value is fixed and should not change.
|
||||
*
|
||||
* @return Value of this enum/flag.
|
||||
*/
|
||||
int getValue();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
/**
|
||||
* A listener which is invoked to notify about events in {@link ILayoutInflater}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IInflateListener {
|
||||
|
||||
/** Called when the inflation process is about to start */
|
||||
void onBeginInflate();
|
||||
|
||||
/**
|
||||
* Called when a view has been inflated
|
||||
*
|
||||
* @param view The view that will be inflated
|
||||
* @param parent The parent of this view
|
||||
*/
|
||||
void onInflateView(IView view, IViewGroup parent);
|
||||
|
||||
/**
|
||||
* Called when the attribute has been applied to the specified view
|
||||
*
|
||||
* @param attr The applied attribute
|
||||
* @param view The view to which attribute was applied
|
||||
*/
|
||||
void onApplyAttribute(IAttribute attr, IView view);
|
||||
|
||||
/**
|
||||
* Called when the inflation process finished successfully
|
||||
*
|
||||
* @param The root rootView of the inflated layout
|
||||
*/
|
||||
void onFinishInflate(IView rootView);
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.itsaky.inflater.util.Preconditions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A layout inflater which inflates layout from a raw XML file
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class ILayoutInflater {
|
||||
|
||||
private static final Pattern XML_DECL = Pattern.compile("<\\?xml.*\\?>");
|
||||
|
||||
/** The file whose content is currently being inflated. */
|
||||
private File currentlyInflatingFile;
|
||||
|
||||
/**
|
||||
* Creates an instance of {@code ILayoutInflater}
|
||||
*
|
||||
* @param config The configuration of the LayoutInflater
|
||||
* @return A new layout inflater instance
|
||||
*/
|
||||
@NonNull
|
||||
public static ILayoutInflater newInstance(LayoutInflaterConfiguration config) {
|
||||
return new XMLLayoutInflater(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the {@link ContextProvider} of this inflater
|
||||
*
|
||||
* @param provider The new {@link ContextProvider}
|
||||
*/
|
||||
public abstract void resetContextProvider(ContextProvider provider);
|
||||
|
||||
/**
|
||||
* Register the inflate listener to this inflater.
|
||||
*
|
||||
* <p>NOTE: Do not register too much listeners as they may affect the inflation time.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
public abstract void registerInflateListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Unregister the inflate listener if it is registered.
|
||||
*
|
||||
* @param listener The listener to unregister
|
||||
*/
|
||||
public abstract void unregisterListener(IInflateListener listener);
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file path
|
||||
*
|
||||
* @param path The file path to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflatePath(String path, ViewGroup parent) throws InflateException {
|
||||
Preconditions.assertNotBlank(path, "Layout file path is blank!");
|
||||
return inflate(new File(path), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout from the given file
|
||||
*
|
||||
* @param file The file to inflate layout from
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
public IView inflate(File file, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotnull(file, "Cannot inflate null file");
|
||||
|
||||
// How can we inflate a non existent file?
|
||||
if (!file.exists()) {
|
||||
throw new InflateException("File does not exist!");
|
||||
}
|
||||
|
||||
if (!file.canRead()) {
|
||||
throw new InflateException("Cannot read file. Permission denied!");
|
||||
}
|
||||
|
||||
// Cannot inflate a non UTF-8 too. This will also be true if the file is empty.
|
||||
if (!FileUtils.isUtf8(file)) {
|
||||
throw new InflateException("File is not UTF-8 or is empty!");
|
||||
}
|
||||
|
||||
requireResourceTable().setInflatingFile(file);
|
||||
this.currentlyInflatingFile = file;
|
||||
return inflate(FileIOUtils.readFile2String(file), parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource finder attached to this layout inflater.
|
||||
*
|
||||
* @return The attached resource finder.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IResourceTable requireResourceTable();
|
||||
|
||||
/**
|
||||
* Inflate the layout from the provided XML layout code
|
||||
*
|
||||
* @param layout The XML layout code
|
||||
* @return The inflated layout
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected IView inflate(String layout, ViewGroup parent) throws InflateException {
|
||||
|
||||
Preconditions.assertNotBlank(layout, "Layout is blank!");
|
||||
|
||||
// Trim the layout code
|
||||
layout = layout.trim();
|
||||
|
||||
final Matcher matcher = XML_DECL.matcher(layout);
|
||||
if (matcher.find()) {
|
||||
layout = layout.substring(matcher.end()).trim();
|
||||
}
|
||||
|
||||
return doInflate(layout, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acatual implementation of the layout inflation
|
||||
*
|
||||
* @param layout The layout to inflate
|
||||
* @return The inflated view
|
||||
* @throws InflateException when there was an error inflating the layout
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract IView doInflate(String layout, ViewGroup parent) throws InflateException;
|
||||
|
||||
/**
|
||||
* Get the currently inflating file.
|
||||
*
|
||||
* @return The file.
|
||||
*/
|
||||
@Nullable
|
||||
protected File getFile() {
|
||||
return currentlyInflatingFile;
|
||||
}
|
||||
|
||||
/** Provides a reference to {@link Context}. This will be used to create original view. */
|
||||
public interface ContextProvider {
|
||||
|
||||
/**
|
||||
* Provide the context to this inflater.
|
||||
*
|
||||
* @return The context
|
||||
*/
|
||||
Context getContext();
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
/** Finds values in resources of a project */
|
||||
public interface IResourceTable {
|
||||
|
||||
/**
|
||||
* Find the path of the drawable with the provided name
|
||||
*
|
||||
* <p>This method will be called if the value of attribute starts with '@drawable/'
|
||||
*
|
||||
* @param name The name of the drawable
|
||||
* @return The drawable resource.
|
||||
*/
|
||||
File findDrawable(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the layout file with the specified name.
|
||||
*
|
||||
* @param name The name of the layout file
|
||||
* @return The found layout resource file
|
||||
*/
|
||||
File findLayout(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find a string resource. Must be a valid string or {@code null}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String findString(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find a color resource. The returned value must be a valid color value or {@code -1}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String findColor(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find an array resource. Must be a valid array or {@code null}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String[] findArray(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the dimension value with the given name.
|
||||
*
|
||||
* @param name The name of the dimension resource
|
||||
* @return The String representation of the dimension. It could one of the following format : 1dp,
|
||||
* 12sp, 123px, etc.
|
||||
*/
|
||||
String findDimension(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the boolean value with the given name.
|
||||
*
|
||||
* @param name The name of the boolean resource.
|
||||
* @return The string value of the boolean resource.
|
||||
*/
|
||||
String findBoolean(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the integer value with the given name.
|
||||
*
|
||||
* @param name The name of the integer resource.
|
||||
* @return The string value of the integer resource.
|
||||
*/
|
||||
String findInteger(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Set the file which is currently being inflated.
|
||||
*
|
||||
* @param file The file.
|
||||
*/
|
||||
void setInflatingFile(@NonNull File file);
|
||||
|
||||
/**
|
||||
* Lists the resources with the given type.
|
||||
*
|
||||
* @param type The type of the resources to list. Might be <code>null</code>.
|
||||
* @return A collection of resource names. If <code>type</code> is <code>null</code>, all the
|
||||
* resources in the resource table must be listed.
|
||||
*/
|
||||
@NonNull
|
||||
Collection<String> listResourceNames(@Nullable String type);
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a view in the UI Designer
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IView {
|
||||
|
||||
/**
|
||||
* The indentation length that will be used while generating layout code. You can modify this to
|
||||
* change the number of spaces used for indentation.
|
||||
*/
|
||||
int DEFAULT_INDENTATION_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* Get this view as android {@link View}. This will be used to display this view in the designer
|
||||
*
|
||||
* @return {@link View} of this object. Maybe {@code null}
|
||||
*/
|
||||
View asView();
|
||||
|
||||
/**
|
||||
* Add this attribute to this view
|
||||
*
|
||||
* @param attr The Attribute to add
|
||||
*/
|
||||
void addAttribute(IAttribute attr);
|
||||
|
||||
/**
|
||||
* Remove this attribute
|
||||
*
|
||||
* @param attr The Attribute to remove
|
||||
*/
|
||||
void removeAttribute(IAttribute attr);
|
||||
|
||||
/**
|
||||
* Remove attribute at index
|
||||
*
|
||||
* @param index Index of the attribute to remove
|
||||
*/
|
||||
void removeAttributeAt(int index);
|
||||
|
||||
/**
|
||||
* Get the attributes of this view.
|
||||
*
|
||||
* @return The attributes.
|
||||
*/
|
||||
List<IAttribute> getAttributes();
|
||||
|
||||
/**
|
||||
* Get the attributes of this view as an array.
|
||||
*
|
||||
* @return The attributes.
|
||||
* @see #getAttributes()
|
||||
*/
|
||||
IAttribute[] getAttrArray();
|
||||
|
||||
/**
|
||||
* Does this view contains an attribute with the given namespace and name?
|
||||
*
|
||||
* @param namespace The namespace to check.
|
||||
* @param name The name of the attribute to check.
|
||||
* @return {@code true} if this view contains the attribute, {@code false} otherwise.
|
||||
*/
|
||||
boolean hasAttribute(INamespace namespace, String name);
|
||||
|
||||
/**
|
||||
* Get the attribute with the given namespace and attribute name. Or {@code null} if there is no
|
||||
* attribute with the given namespace and name.s
|
||||
*
|
||||
* @param namespace The namespace of the attribute.
|
||||
* @param name The name of the attribute.
|
||||
* @return Found {@link IAttribute} or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
IAttribute getAttribute(INamespace namespace, String name);
|
||||
|
||||
/**
|
||||
* Update the value of the given attribute.
|
||||
*
|
||||
* @param attribute The attribute that should be updated.
|
||||
* @return {@code true} if the attribute was successfully updated, {@code false} otherwise.
|
||||
*/
|
||||
default boolean updateAttribute(@NonNull IAttribute attribute) {
|
||||
return updateAttribute(
|
||||
attribute.getNamespace(), attribute.getAttributeName(), attribute.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and update the given attribute with the given value.
|
||||
*
|
||||
* @param namespace The namespace of the attribute.
|
||||
* @param name The name of the attribute.
|
||||
* @param value The new value of the attribute.
|
||||
* @return {@code true} if the attribute was successfully updated, {@code false} otherwise.
|
||||
*/
|
||||
boolean updateAttribute(INamespace namespace, String name, String value);
|
||||
|
||||
/**
|
||||
* Register this attribute adapter
|
||||
*
|
||||
* @param adapter The adapter to register
|
||||
*/
|
||||
void registerAttributeAdapter(IAttributeAdapter adapter);
|
||||
|
||||
/**
|
||||
* Register the given namespace to this view.
|
||||
*
|
||||
* @param namespace The name space to register.
|
||||
*/
|
||||
void registerNamespace(INamespace namespace);
|
||||
|
||||
/**
|
||||
* Find the namespace registered by the given name. Returns <code>null</code> if there is none.
|
||||
*
|
||||
* @param name The name of the namespace to look for. Must not be <code>null</code>.
|
||||
* @return The found namespace or <code>null</code>.
|
||||
*/
|
||||
@Nullable
|
||||
INamespace findRegisteredNamespace(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Get the parent of this view
|
||||
*
|
||||
* @return The parent of this view or {@code null} if this is the root view.
|
||||
*/
|
||||
IViewGroup getParent();
|
||||
|
||||
/**
|
||||
* Set the parent of this view. This is set by {@link IViewGroup} when {@link
|
||||
* IViewGroup#addView(IView, int)} is called.
|
||||
*
|
||||
* @param newParent The new parent of this view.
|
||||
*/
|
||||
void setParent(IViewGroup newParent);
|
||||
|
||||
/**
|
||||
* Remove this view from its parent. This should take care of removing the actual {@link View}
|
||||
* from its parent.
|
||||
*
|
||||
* @return {@code true} if the view was successfully removed, {@code false} otherwise.
|
||||
*/
|
||||
boolean removeFromParent();
|
||||
|
||||
/**
|
||||
* Is this view a placeholder for another view?
|
||||
*
|
||||
* @return {@code true} if this view is a placeholder, {@code false} otherwise.
|
||||
*/
|
||||
boolean isPlaceholder();
|
||||
|
||||
/**
|
||||
* Get the stored data.
|
||||
*
|
||||
* @return The stored data.
|
||||
* @see #setExtraData(Object)
|
||||
*/
|
||||
@Nullable
|
||||
Object getExtraData();
|
||||
|
||||
/**
|
||||
* Store an object that is associated with this view.
|
||||
*
|
||||
* @param data The data to store.
|
||||
*/
|
||||
void setExtraData(Object data);
|
||||
|
||||
/**
|
||||
* @see #generateCode(int)
|
||||
*/
|
||||
default String generateCode() {
|
||||
return generateCode(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout resource code for this file.
|
||||
*
|
||||
* @param indentationLength The number of spaces to indent.
|
||||
*/
|
||||
String generateCode(int indentationLength);
|
||||
|
||||
/**
|
||||
* Get the tag name which will be used for generating XML Code for this view.
|
||||
*
|
||||
* <p>Default implementation simply returns the simple name of this view's class.
|
||||
*
|
||||
* @return The tag name of this view.
|
||||
*/
|
||||
String getXmlTag();
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a {@link android.view.ViewGroup}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IViewGroup extends IView {
|
||||
|
||||
/**
|
||||
* Add this view to this group
|
||||
*
|
||||
* @param view The view to add
|
||||
*/
|
||||
void addView(IView view);
|
||||
|
||||
/**
|
||||
* Add this view to this group
|
||||
*
|
||||
* @param view The view to add
|
||||
* @param index Add this view to this index
|
||||
*/
|
||||
void addView(IView view, int index);
|
||||
|
||||
/**
|
||||
* Remove the view at the specified index
|
||||
*
|
||||
* @param index The index of the view to remove
|
||||
*/
|
||||
void removeView(int index);
|
||||
|
||||
/**
|
||||
* Remove the given if it exists in this group
|
||||
*
|
||||
* @param view The view to remove
|
||||
*/
|
||||
void removeView(IView view);
|
||||
|
||||
/** Remove all children in this group */
|
||||
void removeAll();
|
||||
|
||||
/**
|
||||
* Perform an action in every child in this group
|
||||
*
|
||||
* @param consumer The consumer which will consume the views
|
||||
*/
|
||||
void forEachChild(Consumer<IView> consumer);
|
||||
|
||||
/**
|
||||
* Get the number of child views this layout contains
|
||||
*
|
||||
* @return The child count
|
||||
*/
|
||||
int getChildCount();
|
||||
|
||||
/**
|
||||
* Get the list of children in this group
|
||||
*
|
||||
* @return The list of children
|
||||
*/
|
||||
List<IView> getChildren();
|
||||
|
||||
/**
|
||||
* Get child at the provided index
|
||||
*
|
||||
* @param index The index of child
|
||||
* @throws IndexOutOfBoundsException If there is no child at the given index
|
||||
*/
|
||||
IView getChildAt(int index);
|
||||
|
||||
/**
|
||||
* Find the index of the given children in this group.
|
||||
*
|
||||
* @param view The view to find index of.
|
||||
* @return The index of the child or -1 if this group does not contain the given child.
|
||||
*/
|
||||
int indexOfChild(IView view);
|
||||
|
||||
/**
|
||||
* Find the index of the given children in this group.
|
||||
*
|
||||
* @param view The view to find index of.
|
||||
* @return The index of the child or -1 if this group does not contain the given child.
|
||||
*/
|
||||
int indexOfChild(View view);
|
||||
|
||||
/**
|
||||
* Register the given hierarchy change listener to this group.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
void registerHierarchyChangeListener(OnHierarchyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Unregister the given hierarchy change listener from this view group.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
void unregisterHierarchyChangeListener(OnHierarchyChangeListener listener);
|
||||
|
||||
/**
|
||||
* A listener which can be used to get notification when a children is added or removed from an
|
||||
* {@link IViewGroup}.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public static interface OnHierarchyChangeListener {
|
||||
|
||||
/**
|
||||
* Called when a child view is added in the view group to which this listener is attached.
|
||||
*
|
||||
* @param view The view that was added.
|
||||
*/
|
||||
void onViewAdded(IView view);
|
||||
|
||||
/**
|
||||
* Called when a child view is removed from the view group to which this listener is attached.
|
||||
*
|
||||
* @param view The view that was removed. Note that this view's parent will be null.
|
||||
*/
|
||||
void onViewRemoved(IView view);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
/** Thrown when there was an error inflating the layout */
|
||||
public class InflateException extends RuntimeException {
|
||||
|
||||
public InflateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InflateException(Throwable th) {
|
||||
super(th);
|
||||
}
|
||||
|
||||
public InflateException(String msg, Throwable th) {
|
||||
super(msg, th);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
/** Configuration for {@link ILayoutInflater} */
|
||||
public class LayoutInflaterConfiguration {
|
||||
|
||||
final Set<File> resDirs;
|
||||
final AttrInfo attrInfo;
|
||||
final WidgetInfo widgetInfo;
|
||||
final IResourceTable resFinder;
|
||||
final ILayoutInflater.ContextProvider contextProvider;
|
||||
|
||||
public LayoutInflaterConfiguration(
|
||||
Set<File> resDirs,
|
||||
AttrInfo attrInfo,
|
||||
WidgetInfo widgetInfo,
|
||||
IResourceTable resourceProvider,
|
||||
ILayoutInflater.ContextProvider contextProvider) {
|
||||
this.resDirs = resDirs;
|
||||
this.attrInfo = attrInfo;
|
||||
this.widgetInfo = widgetInfo;
|
||||
this.resFinder = resourceProvider;
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
/** A class that builds a {@link LayoutInflaterConfiguration} */
|
||||
public static class Builder {
|
||||
|
||||
private Set<File> resDirs;
|
||||
private AttrInfo attrInfo;
|
||||
private WidgetInfo widgetInfo;
|
||||
private IResourceTable resourceProvider;
|
||||
private ILayoutInflater.ContextProvider provider;
|
||||
|
||||
public Builder setResourceDirectories(Set<File> dirs) {
|
||||
this.resDirs = dirs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAttrInfo(AttrInfo info) {
|
||||
this.attrInfo = info;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setWidgetInfo(WidgetInfo info) {
|
||||
this.widgetInfo = info;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResourceFinder(IResourceTable provider) {
|
||||
this.resourceProvider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setContextProvider(ILayoutInflater.ContextProvider provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LayoutInflaterConfiguration create() {
|
||||
return new LayoutInflaterConfiguration(
|
||||
resDirs, attrInfo, widgetInfo, resourceProvider, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater;
|
||||
|
||||
/**
|
||||
* Exception thrown by inflaters and drawable parsers to indicate that the a specific view or tag is
|
||||
* not supported.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class NotSupportedException extends UnsupportedOperationException {
|
||||
|
||||
public NotSupportedException() {}
|
||||
|
||||
public NotSupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NotSupportedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NotSupportedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import static com.itsaky.inflater.util.Preconditions.assertNotBlank;
|
||||
import static com.itsaky.inflater.util.Preconditions.assertNotnull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.blankj.utilcode.util.ReflectUtils;
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
import com.itsaky.inflater.impl.BaseView;
|
||||
import com.itsaky.inflater.impl.ErrorUiView;
|
||||
import com.itsaky.inflater.impl.IncludeLayout;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.inflater.impl.UiView;
|
||||
import com.itsaky.inflater.impl.UiViewGroup;
|
||||
import com.itsaky.inflater.util.Preconditions;
|
||||
import com.itsaky.widgets.WidgetInfo;
|
||||
import com.itsaky.widgets.models.Widget;
|
||||
import com.itsaky.xml.INamespace;
|
||||
import com.itsaky.xml.impl.UiNamespace;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Attribute;
|
||||
import org.jsoup.nodes.Attributes;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.parser.Parser;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
|
||||
public static final String ATTR_ADAPTER_SUFFIX = "AttrAdapter";
|
||||
private static final ILogger LOG = ILogger.newInstance("XMLLayoutInflater");
|
||||
private final Set<File> resDirs;
|
||||
private final AttrInfo attrInfo;
|
||||
private final WidgetInfo widgetInfo;
|
||||
private final IResourceTable resFinder;
|
||||
private ContextProvider contextProvider;
|
||||
|
||||
XMLLayoutInflater(@NonNull LayoutInflaterConfiguration config) {
|
||||
this.resDirs = config.resDirs;
|
||||
this.attrInfo = config.attrInfo;
|
||||
this.widgetInfo = config.widgetInfo;
|
||||
this.resFinder = config.resFinder;
|
||||
this.contextProvider = config.contextProvider;
|
||||
|
||||
Preconditions.assertAllNotNull(
|
||||
"LayoutInflater parameters cannot be null", resDirs, attrInfo, widgetInfo, resFinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetContextProvider(ILayoutInflater.ContextProvider provider) {
|
||||
this.contextProvider = provider;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected IResourceTable requireResourceTable() {
|
||||
return this.resFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected IView doInflate(String layout, ViewGroup parent) throws InflateException {
|
||||
try {
|
||||
|
||||
IDTable.newRound();
|
||||
|
||||
// Notify
|
||||
preInflate();
|
||||
|
||||
final Document doc = Jsoup.parse(layout, "", Parser.xmlParser());
|
||||
|
||||
if (doc.childrenSize() <= 0) {
|
||||
throw new InflateException("No views added");
|
||||
}
|
||||
|
||||
if (doc.childrenSize() > 1) {
|
||||
throw new InflateException(
|
||||
"More than one root element was found. An XML layout can have only one root"
|
||||
+ " element.");
|
||||
}
|
||||
|
||||
IView root = onCreateView(doc.child(0), parent, null);
|
||||
|
||||
if (root == null) {
|
||||
root = onCreateErrorView(TextView.class.getName(), "No views added", parent);
|
||||
}
|
||||
|
||||
// Notify
|
||||
postInflate(root);
|
||||
|
||||
return root;
|
||||
} catch (Throwable th) {
|
||||
throw new InflateException("Unable to inflate layout", th);
|
||||
}
|
||||
}
|
||||
|
||||
protected IView onCreateView(Element tag, ViewGroup parentGroup, IViewGroup parent)
|
||||
throws InflateException {
|
||||
if (tag == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String name = tag.tagName().trim();
|
||||
|
||||
if (name.equals("include")) {
|
||||
return createFromInclude(tag.attributes(), parent);
|
||||
}
|
||||
|
||||
final IView root;
|
||||
int style = tag.hasAttr("style") ? parseFrameworkStyle(tag.attr("style")) : -1;
|
||||
if (!name.contains(".")) {
|
||||
root = createFromSimpleName(name, parentGroup, style);
|
||||
} else {
|
||||
root = createFromQualifiedName(name, parentGroup, style);
|
||||
}
|
||||
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
registerAttributeAdaptersTo(root);
|
||||
|
||||
if (!tag.attributes().isEmpty()) {
|
||||
addAttributesTo(root, tag.attributes(), parent);
|
||||
}
|
||||
|
||||
if (root instanceof IViewGroup) {
|
||||
addChildren(tag, (IViewGroup) root);
|
||||
}
|
||||
|
||||
postCreateView(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected void addChildren(@NonNull Element tag, IViewGroup parent) {
|
||||
if (tag.childrenSize() > 0 && !parent.isPlaceholder()) {
|
||||
for (Element child : tag.children()) {
|
||||
final IView created = onCreateView(child, (ViewGroup) parent.asView(), parent);
|
||||
if (created != null) {
|
||||
parent.addView(created);
|
||||
created.setParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseFrameworkStyle(String value) throws InflateException {
|
||||
try {
|
||||
final String name = underscorize(value.substring(value.indexOf('/') + 1));
|
||||
return (int) android.R.attr.class.getField(name).get(null);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to parse framework style", th);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerAttributeAdaptersTo(@NonNull IView root) {
|
||||
root.registerAttributeAdapter(onCreateAttributeAdapter(root.asView()));
|
||||
}
|
||||
|
||||
protected IAttributeAdapter onCreateAttributeAdapter(View view) {
|
||||
final var displayMetrics = contextProvider.getContext().getResources().getDisplayMetrics();
|
||||
try {
|
||||
final String adapterName = view.getClass().getName().concat(ATTR_ADAPTER_SUFFIX);
|
||||
final String name = "com.itsaky.inflater.adapters.".concat(adapterName);
|
||||
final Class<? extends IAttributeAdapter> adapterClass =
|
||||
getClass().getClassLoader().loadClass(name).asSubclass(IAttributeAdapter.class);
|
||||
final var constructor =
|
||||
adapterClass.getConstructor(IResourceTable.class, DisplayMetrics.class);
|
||||
final var adapter = constructor.newInstance(resFinder, displayMetrics);
|
||||
adapter.setResourceFinder(resFinder);
|
||||
return adapter;
|
||||
} catch (Throwable th) {
|
||||
LOG.error(
|
||||
BaseApplication.getBaseInstance()
|
||||
.getString(
|
||||
com.itsaky.androidide.resources.R.string.err_no_attr_adapter, view.getClass().getName()),
|
||||
th);
|
||||
}
|
||||
|
||||
// If we cannot find a suitable adapter, fall back to using common adapters
|
||||
IAttributeAdapter adapter;
|
||||
if (view instanceof ViewGroup) {
|
||||
adapter = new ViewGroupAttrAdapter(resFinder, displayMetrics);
|
||||
} else {
|
||||
adapter = new ViewAttrAdapter(resFinder, displayMetrics);
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
protected void addAttributesTo(IView view, Attributes attributes, IViewGroup parent) {
|
||||
assertNotnull(attributes, "Cannot apply null attributes!");
|
||||
|
||||
for (Attribute attr : attributes) {
|
||||
final String key = attr.getKey();
|
||||
final String[] split =
|
||||
!key.contains(":") ? new String[] {"", key} : key.split(Pattern.quote(":"), 2);
|
||||
final String namespace = split[0];
|
||||
final String name = split[1];
|
||||
final String value = attr.getValue();
|
||||
|
||||
INamespace ns;
|
||||
if (namespace.equals("xmlns")) {
|
||||
ns = INamespace.DECLARATOR;
|
||||
view.registerNamespace(new UiNamespace(name, value));
|
||||
} else {
|
||||
// set the parent so that we could look for
|
||||
// declared namespaces
|
||||
view.setParent(parent);
|
||||
ns = view.findRegisteredNamespace(namespace);
|
||||
}
|
||||
|
||||
if (ns == null) {
|
||||
ns = INamespace.invalid(namespace);
|
||||
}
|
||||
|
||||
final IAttribute iAttr = asAttribute(ns, name, value);
|
||||
view.addAttribute(iAttr);
|
||||
|
||||
postApplyAttribute(iAttr, view);
|
||||
}
|
||||
}
|
||||
|
||||
protected IAttribute asAttribute(INamespace namespace, String name, String value) {
|
||||
return new UiAttribute(namespace, name, value);
|
||||
}
|
||||
|
||||
protected IView create(@NonNull String name, ViewGroup parent, int style)
|
||||
throws InflateException {
|
||||
if (!name.contains(".")) {
|
||||
return createFromSimpleName(name, parent, style);
|
||||
} else {
|
||||
return createFromQualifiedName(name, parent, style);
|
||||
}
|
||||
}
|
||||
|
||||
protected IView createFromInclude(Attributes attrs, IViewGroup parent) {
|
||||
|
||||
if (!attrs.hasKey("layout")) {
|
||||
throw new InflateException("<include> tag doesn't have 'layout' attribute.");
|
||||
}
|
||||
|
||||
final var layout = attrs.get("layout");
|
||||
Preconditions.assertNotBlank(
|
||||
layout,
|
||||
"'layout' attribute for <include> tag has invalid value." + "layout='" + layout + "'");
|
||||
|
||||
if (layout.startsWith("@android:layout/")) {
|
||||
return inflateAndroidLayout(layout.substring("@android:layout/".length()), attrs, parent);
|
||||
}
|
||||
|
||||
final var name = layout.substring("@layout/".length());
|
||||
final var file = resFinder.findLayout(name);
|
||||
if (file == null) {
|
||||
throw new InflateException("Layout file '" + name + "' not found");
|
||||
}
|
||||
|
||||
return inflateLocalIncludeLayout(attrs, parent, name, file);
|
||||
}
|
||||
|
||||
protected IView inflateAndroidLayout(String name, Attributes attrs, @NonNull IViewGroup parent) {
|
||||
final var id = (int) ReflectUtils.reflect(android.R.layout.class).field(name).get();
|
||||
final var view = LayoutInflater.from(contextProvider.getContext()).inflate(id, null);
|
||||
final var layout = new IncludeLayout(new UiView(IncludeLayout.TAG, view, false));
|
||||
|
||||
applyLayoutParams(layout, (ViewGroup) parent.asView());
|
||||
addAttributesTo(layout, attrs, parent);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
protected IView createFromSimpleName(String name, ViewGroup parent, int style)
|
||||
throws InflateException {
|
||||
final Widget widget = widgetInfo.getWidgetBySimpleName(name);
|
||||
if (widget == null) {
|
||||
LOG.error("Unable to inflate view. widget == null");
|
||||
return onCreateErrorView(
|
||||
name, getString(com.itsaky.androidide.resources.R.string.msg_cannot_create_view, name), parent);
|
||||
}
|
||||
|
||||
return createFromQualifiedName(widget.name, parent, style);
|
||||
}
|
||||
|
||||
protected IView createFromQualifiedName(String name, ViewGroup parent, int style)
|
||||
throws InflateException {
|
||||
assertNotBlank(name, "Invalid tag name: " + name);
|
||||
|
||||
// TODO Try to load classes directly from .class files if possible
|
||||
try {
|
||||
|
||||
final var androidView = this.widgetInfo.getWidget(name);
|
||||
if (androidView == null) {
|
||||
// If this is not an Android view, do not bother to create one
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
final View created = createAndroidViewForName(name, style);
|
||||
final BaseView view =
|
||||
created instanceof ViewGroup
|
||||
? new UiViewGroup(name, (ViewGroup) created)
|
||||
: new UiView(name, created);
|
||||
return applyLayoutParams(view, parent);
|
||||
} catch (Throwable th) {
|
||||
|
||||
if (th instanceof NotSupportedException) {
|
||||
return onCreateErrorView(name, getString(R.string.msg_view_not_supported, name), parent);
|
||||
}
|
||||
|
||||
final var msg = getString(R.string.msg_cannot_create_view, name);
|
||||
LOG.error(msg, th);
|
||||
return onCreateErrorView(name, msg, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected View createAndroidViewForName(String name, int style)
|
||||
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
|
||||
InstantiationException, java.lang.reflect.InvocationTargetException {
|
||||
final Class<? extends View> loaded = Class.forName(name).asSubclass(View.class);
|
||||
final Constructor<? extends View> constructor;
|
||||
if (style == -1) {
|
||||
constructor = loaded.getConstructor(Context.class);
|
||||
return constructor.newInstance(contextProvider.getContext());
|
||||
} else {
|
||||
constructor = loaded.getConstructor(Context.class, AttributeSet.class, int.class);
|
||||
return constructor.newInstance(contextProvider.getContext(), null, style);
|
||||
}
|
||||
}
|
||||
|
||||
protected IView applyLayoutParams(@NonNull IView view, ViewGroup parent) {
|
||||
final ViewGroup.LayoutParams params = generateLayoutParams(parent);
|
||||
view.asView().setLayoutParams(params);
|
||||
return view;
|
||||
}
|
||||
|
||||
protected IView onCreateErrorView(String name, String msg, final ViewGroup parent)
|
||||
throws InflateException {
|
||||
final var view =
|
||||
ErrorUiView.create(
|
||||
Objects.requireNonNull(contextProvider.getContext(), "Context is null."), name, msg);
|
||||
return applyLayoutParams(view, parent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup parent) {
|
||||
try {
|
||||
Class<?> clazz = parent.getClass();
|
||||
Method method = null;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
method = clazz.getDeclaredMethod("generateDefaultLayoutParams");
|
||||
break;
|
||||
} catch (Throwable e) {
|
||||
/* ignored */
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
|
||||
if (clazz == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (method != null) {
|
||||
method.setAccessible(true);
|
||||
return (ViewGroup.LayoutParams) method.invoke(parent);
|
||||
}
|
||||
|
||||
LOG.error("Unable to create default params for view parent:", parent);
|
||||
return new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
} catch (Throwable th) {
|
||||
throw new InflateException("Unable to create layout params for parent: " + parent, th);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String underscorize(@NonNull String substring) {
|
||||
return substring.replace(".", "_");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private IncludeLayout inflateLocalIncludeLayout(
|
||||
Attributes attrs, IViewGroup parent, String name, File file) {
|
||||
|
||||
// DesignerActivity applies drag listeners in 'onInflateView()' call
|
||||
// But, <include> layouts must not be edited here
|
||||
// So, we avoid notifying the listeners
|
||||
super.notify = false;
|
||||
|
||||
final var view = this.inflate(file, (ViewGroup) parent.asView());
|
||||
Preconditions.assertNotnull(view, "Failed to inflate '" + name + "'");
|
||||
|
||||
final var includeLayout = new IncludeLayout(view);
|
||||
|
||||
applyLayoutParams(includeLayout, (ViewGroup) parent.asView());
|
||||
addAttributesTo(includeLayout, attrs, parent);
|
||||
|
||||
// Enable notifications
|
||||
super.notify = true;
|
||||
|
||||
return includeLayout;
|
||||
}
|
||||
|
||||
private String getString(@StringRes int id, Object... format) throws InflateException {
|
||||
assertNotnull(contextProvider.getContext(), "Context is null!");
|
||||
return contextProvider.getContext().getString(id, format);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# Attribute Adapters
|
||||
|
||||
Attribute adapters handle attributes for a specific view.
|
||||
|
||||
## Implementing an Adapter
|
||||
|
||||
### Basic
|
||||
|
||||
To implment an adapter, you'll have to write a class extending one of the existing adapters.
|
||||
|
||||
Adapters extend other adapters in the same way as Android's view classes extend other views. For
|
||||
example, ```android.widget.CompoundButton``` extends ```android.widget.Button```, in the same way
|
||||
```com.itsaky.layoutinflater.adapters.android.widget.CompoundButtonAttrAdapter``` extends
|
||||
```com.itsaky.layoutinflater.adapters.android.widget.ButtonAttrAdapter```. In this way, we don't
|
||||
have to rewrite whole logic again to implement adapters for every other widget.
|
||||
|
||||
### Naming an adapter
|
||||
|
||||
If you look at the package name and the simple name of the adapter classes mentioned above, you'll
|
||||
find that there is a specific way an adapter is named.
|
||||
|
||||
An adpater should be named in the following way :
|
||||
```com.itsaky.layoutinflater.adapters.<package_name>.<simple_name>AttrAdapter```,
|
||||
where, ```<package_name>``` is the package name of the widget you are implementing an adapter for
|
||||
and ```<simple_name>``` is the simple name of the widget. The simple name is then suffixed
|
||||
with ```AttrAdapter```.
|
||||
|
||||
Here is an example :
|
||||
Assume we have to implement an adapter for ```android.widget.Button```. Here, ```<package_name>```
|
||||
is ```android.widget``` and
|
||||
```<simple_name>``` is ```Button```.
|
||||
|
||||
So, the adapter implentation class should
|
||||
be ```com.itsaky.layoutinflater.adapters.android.widget.ButtonAttrAdapter```.
|
||||
|
||||
### Why such naming?
|
||||
|
||||
We use reflection to automatically create an adapter when inflating a view. For example,
|
||||
if ```android.widget.Button``` instance is being created, we could simply create an adapter instance
|
||||
using reflection by getting the package name and simple name of the widget.
|
||||
|
||||
See ```XMLLayoutInflater#onCreateAttributeAdapter(View)```.
|
||||
|
||||
### Precautions
|
||||
|
||||
- Make sure you correctly spell the file names. As you might already know, file names are case
|
||||
sensitive.
|
||||
- Some methods (or even classes) are not available for earlier versions of Android. The classes or
|
||||
methods you're using must be compatible with API 24.
|
||||
|
||||
You could also check if you can use a method. Some methods are already implemented in
|
||||
ViewAttrAdapter so you won't have to rewrite them. For example:
|
||||
|
||||
```
|
||||
isApi26() // Android 8 (Oreo)
|
||||
isApi28() // Android 9 (Pie)
|
||||
isApi29() // Android 10 (Q)
|
||||
isApi30() // Android 11 (R)
|
||||
```
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.gesture;
|
||||
|
||||
import android.gesture.GestureOverlayView;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ReflectUtils;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.widget.FrameLayoutAttrAdapter;
|
||||
|
||||
/**
|
||||
* Attribute adapter for {@link GestureOverlayView}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class GestureOverlayViewAttrAdapter extends FrameLayoutAttrAdapter {
|
||||
|
||||
public GestureOverlayViewAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof GestureOverlayView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final var gesture = (GestureOverlayView) view;
|
||||
final var name = attribute.getAttributeName();
|
||||
final var value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "eventsInterceptionEnabled":
|
||||
gesture.setEventsInterceptionEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "fadeDuration":
|
||||
ReflectUtils.reflect(gesture).field("mFadeDuration", parseLong(value, 150));
|
||||
break;
|
||||
case "fadeEnabled":
|
||||
gesture.setFadeEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "fadeOffset":
|
||||
gesture.setFadeOffset(parseLong(value, 420));
|
||||
break;
|
||||
case "gestureColor":
|
||||
gesture.setGestureColor(parseColor(value, gesture.getContext()));
|
||||
break;
|
||||
case "gestureStrokeAngleThreshold":
|
||||
gesture.setGestureStrokeAngleThreshold(parseFloat(value));
|
||||
break;
|
||||
case "gestureStrokeLengthThreshold":
|
||||
gesture.setGestureStrokeLengthThreshold(parseFloat(value));
|
||||
break;
|
||||
case "gestureStrokeSquarenessThreshold":
|
||||
gesture.setGestureStrokeSquarenessTreshold(parseFloat(value));
|
||||
break;
|
||||
case "gestureStrokeType":
|
||||
gesture.setGestureStrokeType(parseGestureStrokeType(value));
|
||||
break;
|
||||
case "gestureStrokeWidth":
|
||||
gesture.setGestureStrokeWidth(parseFloat(value));
|
||||
break;
|
||||
case "orientation":
|
||||
gesture.setOrientation(parseOrientation(value));
|
||||
break;
|
||||
case "uncertainGestureColor":
|
||||
gesture.setUncertainGestureColor(parseColor(value, gesture.getContext()));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private int parseOrientation(String value) {
|
||||
if ("horizontal".equals(value)) {
|
||||
return GestureOverlayView.ORIENTATION_HORIZONTAL;
|
||||
}
|
||||
|
||||
return GestureOverlayView.ORIENTATION_VERTICAL;
|
||||
}
|
||||
|
||||
protected int parseGestureStrokeType(String value) {
|
||||
if ("multiple".equals(value)) {
|
||||
return GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE;
|
||||
}
|
||||
return GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE;
|
||||
}
|
||||
}
|
||||
-595
@@ -1,595 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IAttributeAdapter;
|
||||
import com.itsaky.inflater.IDTable;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.util.CommonParseUtils;
|
||||
import com.itsaky.xml.INamespace;
|
||||
|
||||
/**
|
||||
* Handles attributes common to all android views. Also, LayoutParams related issues are also
|
||||
* handled by this adapter.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ViewAttrAdapter extends CommonParseUtils implements IAttributeAdapter {
|
||||
|
||||
protected static final ILogger LOG = ILogger.newInstance("BaseViewAttrAdapter");
|
||||
|
||||
public ViewAttrAdapter(@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceFinder(IResourceTable resourceFinder) {
|
||||
this.resourceFinder = resourceFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return true; // Can be applied to any view
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final INamespace namespace = attribute.getNamespace();
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
final ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
final Context context = view.getContext();
|
||||
|
||||
if (!canHandleNamespace(namespace)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "layout_height":
|
||||
params.height = parseDimension(value, -2, displayMetrics);
|
||||
break;
|
||||
case "layout_width":
|
||||
params.width = parseDimension(value, -2, displayMetrics);
|
||||
break;
|
||||
case "alpha":
|
||||
view.setAlpha(parseFloat(value));
|
||||
break;
|
||||
case "background":
|
||||
view.setBackground(parseDrawable(value, context));
|
||||
break;
|
||||
case "backgroundTint":
|
||||
view.setBackgroundTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "backgroundTintMode":
|
||||
view.setBackgroundTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "clipToOutline":
|
||||
view.setClipToOutline(parseBoolean(value));
|
||||
break;
|
||||
case "contentDescription":
|
||||
view.setContentDescription(parseString(value));
|
||||
break;
|
||||
case "contextClickable":
|
||||
view.setContextClickable(parseBoolean(value));
|
||||
break;
|
||||
case "defaultFocusHighlightEnabled":
|
||||
view.setDefaultFocusHighlightEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "drawingCacheQuality":
|
||||
view.setDrawingCacheQuality(parseDrawingCacheQuality(value));
|
||||
break;
|
||||
case "duplicateParentState":
|
||||
view.setDuplicateParentStateEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "elevation":
|
||||
view.setElevation(parseDimension(value, 0, displayMetrics));
|
||||
break;
|
||||
case "fadeScrollbars":
|
||||
view.setScrollbarFadingEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "fadingEdgeLength":
|
||||
view.setFadingEdgeLength(parseDimension(value, 0, displayMetrics));
|
||||
break;
|
||||
case "filterTouchesWhenObscured":
|
||||
view.setFilterTouchesWhenObscured(parseBoolean(value));
|
||||
break;
|
||||
case "foreground":
|
||||
view.setForeground(parseDrawable(value, context));
|
||||
break;
|
||||
case "foregroundGravity":
|
||||
view.setForegroundGravity(parseGravity(value));
|
||||
break;
|
||||
case "foregroundTint":
|
||||
view.setForegroundTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "foregroundTintMode":
|
||||
view.setForegroundTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "id":
|
||||
view.setId(parseId(value));
|
||||
break;
|
||||
case "minHeight":
|
||||
view.setMinimumHeight(parseDimension(value, 0, displayMetrics));
|
||||
break;
|
||||
case "minWidth":
|
||||
view.setMinimumWidth(parseDimension(value, 0, displayMetrics));
|
||||
break;
|
||||
case "padding":
|
||||
final int padding = parseDimension(value, 0, displayMetrics);
|
||||
view.setPaddingRelative(padding, padding, padding, padding);
|
||||
break;
|
||||
case "paddingLeft":
|
||||
final int paddingLeft = parseDimension(value, 0, displayMetrics);
|
||||
view.setPadding(
|
||||
paddingLeft, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom());
|
||||
break;
|
||||
case "paddingTop":
|
||||
final int paddingTop = parseDimension(value, 0, displayMetrics);
|
||||
view.setPadding(
|
||||
view.getPaddingLeft(), paddingTop, view.getPaddingRight(), view.getPaddingBottom());
|
||||
break;
|
||||
case "paddingRight":
|
||||
final int paddingRight = parseDimension(value, 0, displayMetrics);
|
||||
view.setPadding(
|
||||
view.getPaddingLeft(), view.getPaddingTop(), paddingRight, view.getPaddingBottom());
|
||||
break;
|
||||
case "paddingBottom":
|
||||
final int paddingBottom = parseDimension(value, 0, displayMetrics);
|
||||
view.setPadding(
|
||||
view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), paddingBottom);
|
||||
break;
|
||||
case "paddingStart":
|
||||
final int paddingStart = parseDimension(value, 0, displayMetrics);
|
||||
view.setPaddingRelative(
|
||||
paddingStart, view.getPaddingTop(), view.getPaddingEnd(), view.getPaddingBottom());
|
||||
break;
|
||||
case "paddingEnd":
|
||||
final int paddingEnd = parseDimension(value, 0, displayMetrics);
|
||||
view.setPaddingRelative(
|
||||
view.getPaddingStart(), view.getPaddingTop(), paddingEnd, view.getPaddingBottom());
|
||||
break;
|
||||
case "rotation":
|
||||
view.setRotation(parseFloat(value));
|
||||
break;
|
||||
case "rotationX":
|
||||
view.setRotationX(parseFloat(value));
|
||||
break;
|
||||
case "rotationY":
|
||||
view.setRotationY(parseFloat(value));
|
||||
break;
|
||||
case "scaleX":
|
||||
view.setScaleX(parseFloat(value));
|
||||
break;
|
||||
case "scaleY":
|
||||
view.setScaleY(parseFloat(value));
|
||||
break;
|
||||
case "scrollX":
|
||||
view.setScrollX((int) parseFloat(value));
|
||||
break;
|
||||
case "scrollY":
|
||||
view.setScrollY((int) parseFloat(value));
|
||||
break;
|
||||
case "textAlignment":
|
||||
view.setTextAlignment(parseTextAlignment(value));
|
||||
break;
|
||||
case "textDirection":
|
||||
view.setTextDirection(parseTextDirection(value));
|
||||
break;
|
||||
case "tooltipText":
|
||||
view.setTooltipText(parseString(value));
|
||||
break;
|
||||
case "transformPivotX":
|
||||
view.setPivotX(parseFloat(value));
|
||||
break;
|
||||
case "transformPivotY":
|
||||
view.setPivotY(parseFloat(value));
|
||||
break;
|
||||
case "translationX":
|
||||
view.setTranslationX(parseFloat(value));
|
||||
break;
|
||||
case "translationY":
|
||||
view.setTranslationY(parseFloat(value));
|
||||
break;
|
||||
case "translationZ":
|
||||
view.setTranslationZ(parseFloat(value));
|
||||
break;
|
||||
case "visibility":
|
||||
view.setVisibility(parseVisibility(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// ----- Handle attributes related to parent ------
|
||||
|
||||
if (!handled && params instanceof LinearLayout.LayoutParams) {
|
||||
handled =
|
||||
handleLinearLayoutParams((LinearLayout.LayoutParams) params, name, value, displayMetrics);
|
||||
}
|
||||
|
||||
if (!handled && params instanceof RelativeLayout.LayoutParams) {
|
||||
handled = handleRelativeLayoutParams((RelativeLayout.LayoutParams) params, name, value);
|
||||
}
|
||||
|
||||
if (!handled && params instanceof FrameLayout.LayoutParams) {
|
||||
handled = handleFrameLayoutParams((FrameLayout.LayoutParams) params, name, value);
|
||||
}
|
||||
|
||||
if (!handled && params instanceof ViewGroup.MarginLayoutParams) {
|
||||
handled =
|
||||
handleMarginParams((ViewGroup.MarginLayoutParams) params, name, value, displayMetrics);
|
||||
}
|
||||
|
||||
view.setLayoutParams(params);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected int parseId(@NonNull String value) {
|
||||
if (value.startsWith("@id/")) {
|
||||
final String name = value.substring("@id/".length());
|
||||
return IDTable.getId(name);
|
||||
} else if (value.startsWith("@+id/")) {
|
||||
final String name = value.substring("@+id/".length());
|
||||
return IDTable.newId(name);
|
||||
}
|
||||
return View.NO_ID;
|
||||
}
|
||||
|
||||
protected boolean handleRelativeLayoutParams(
|
||||
RelativeLayout.LayoutParams params, @NonNull String name, String value) {
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "layout_above":
|
||||
params.addRule(RelativeLayout.ABOVE, parseId(value));
|
||||
break;
|
||||
case "layout_alignBaseline":
|
||||
params.addRule(RelativeLayout.ALIGN_BASELINE, parseId(value));
|
||||
break;
|
||||
case "layout_alignBottom":
|
||||
params.addRule(RelativeLayout.ALIGN_BOTTOM, parseId(value));
|
||||
break;
|
||||
case "layout_alignEnd":
|
||||
params.addRule(RelativeLayout.ALIGN_END, parseId(value));
|
||||
break;
|
||||
case "layout_alignLeft":
|
||||
params.addRule(RelativeLayout.ALIGN_LEFT, parseId(value));
|
||||
break;
|
||||
case "layout_alignParentTop":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_TOP, params);
|
||||
break;
|
||||
case "layout_alignParentBottom":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_BOTTOM, params);
|
||||
break;
|
||||
case "layout_alignParentStart":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_START, params);
|
||||
break;
|
||||
case "layout_alignParentEnd":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_END, params);
|
||||
break;
|
||||
case "layout_alignParentLeft":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_LEFT, params);
|
||||
break;
|
||||
case "layout_alignParentRight":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_RIGHT, params);
|
||||
break;
|
||||
case "layout_alignRight":
|
||||
params.addRule(RelativeLayout.ALIGN_RIGHT, parseId(value));
|
||||
break;
|
||||
case "layout_alignStart":
|
||||
params.addRule(RelativeLayout.ALIGN_START, parseId(value));
|
||||
break;
|
||||
case "layout_alignTop":
|
||||
params.addRule(RelativeLayout.ALIGN_TOP, parseId(value));
|
||||
break;
|
||||
case "layout_alignWithParentIfMissing":
|
||||
params.alignWithParent = parseBoolean(value);
|
||||
break;
|
||||
case "layout_below":
|
||||
params.addRule(RelativeLayout.BELOW, parseId(value));
|
||||
break;
|
||||
case "layout_centerHorizontal":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_HORIZONTAL, params);
|
||||
break;
|
||||
case "layout_centerInParent":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_IN_PARENT, params);
|
||||
break;
|
||||
case "layout_centerVertical":
|
||||
setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_VERTICAL, params);
|
||||
break;
|
||||
case "layout_toEndOf":
|
||||
params.addRule(RelativeLayout.END_OF, parseId(value));
|
||||
break;
|
||||
case "layout_toStartOf":
|
||||
params.addRule(RelativeLayout.START_OF, parseId(value));
|
||||
break;
|
||||
case "layout_toLeftOf":
|
||||
params.addRule(RelativeLayout.LEFT_OF, parseId(value));
|
||||
break;
|
||||
case "layout_toRightOf":
|
||||
params.addRule(RelativeLayout.RIGHT_OF, parseId(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected boolean handleLinearLayoutParams(
|
||||
LinearLayout.LayoutParams params, @NonNull String name, String value, DisplayMetrics dm) {
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "layout_gravity":
|
||||
params.gravity = parseGravity(value);
|
||||
break;
|
||||
case "layout_weight":
|
||||
params.weight = parseDimension(value, 1, dm);
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected boolean handleFrameLayoutParams(
|
||||
FrameLayout.LayoutParams params, @NonNull String name, String value) {
|
||||
boolean handled = true;
|
||||
|
||||
if ("layout_gravity".equals(name)) {
|
||||
params.gravity = parseGravity(value);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected boolean handleMarginParams(
|
||||
ViewGroup.MarginLayoutParams params, @NonNull String name, String value, DisplayMetrics dm) {
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "layout_margin":
|
||||
final int margin = parseDimension(value, 0, dm);
|
||||
params.setMargins(margin, margin, margin, margin);
|
||||
break;
|
||||
case "layout_marginLeft":
|
||||
params.leftMargin = parseDimension(value, 0, dm);
|
||||
break;
|
||||
case "layout_marginTop":
|
||||
params.topMargin = parseDimension(value, 0, dm);
|
||||
break;
|
||||
case "layout_marginRight":
|
||||
params.rightMargin = parseDimension(value, 0, dm);
|
||||
break;
|
||||
case "layout_marginBottom":
|
||||
params.bottomMargin = parseDimension(value, 0, dm);
|
||||
break;
|
||||
case "layout_marginStart":
|
||||
params.setMarginStart(parseDimension(value, 0, dm));
|
||||
break;
|
||||
case "layout_marginEnd":
|
||||
params.setMarginEnd(parseDimension(value, 0, dm));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected boolean canHandleNamespace(@NonNull IAttribute attr) {
|
||||
return canHandleNamespace(attr.getNamespace());
|
||||
}
|
||||
|
||||
protected boolean canHandleNamespace(INamespace namespace) {
|
||||
return INamespace.ANDROID.equals(namespace);
|
||||
}
|
||||
|
||||
protected int parseDrawingCacheQuality(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "high":
|
||||
return View.DRAWING_CACHE_QUALITY_HIGH;
|
||||
case "low":
|
||||
return View.DRAWING_CACHE_QUALITY_LOW;
|
||||
case "auto":
|
||||
default:
|
||||
return View.DRAWING_CACHE_QUALITY_AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseVisibility(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "gone":
|
||||
return View.GONE;
|
||||
case "invisible":
|
||||
return View.INVISIBLE;
|
||||
case "visible":
|
||||
default:
|
||||
return View.VISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseTextAlignment(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "center":
|
||||
return View.TEXT_ALIGNMENT_CENTER;
|
||||
case "gravity":
|
||||
return View.TEXT_ALIGNMENT_GRAVITY;
|
||||
case "textEnd":
|
||||
return View.TEXT_ALIGNMENT_TEXT_END;
|
||||
case "textStart":
|
||||
return View.TEXT_ALIGNMENT_TEXT_START;
|
||||
case "viewEnd":
|
||||
return View.TEXT_ALIGNMENT_VIEW_END;
|
||||
case "viewStart":
|
||||
return View.TEXT_ALIGNMENT_VIEW_START;
|
||||
case "inherit":
|
||||
default:
|
||||
return View.TEXT_ALIGNMENT_INHERIT;
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseTextDirection(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "anyRtl":
|
||||
return View.TEXT_DIRECTION_ANY_RTL;
|
||||
case "firstStrong":
|
||||
return View.TEXT_DIRECTION_FIRST_STRONG;
|
||||
case "firstStrongLtr":
|
||||
return View.TEXT_DIRECTION_FIRST_STRONG_LTR;
|
||||
case "firstStrongRtl":
|
||||
return View.TEXT_DIRECTION_FIRST_STRONG_RTL;
|
||||
case "locale":
|
||||
return View.TEXT_DIRECTION_LOCALE;
|
||||
case "ltr":
|
||||
return View.TEXT_DIRECTION_LTR;
|
||||
case "rtl":
|
||||
return View.TEXT_DIRECTION_RTL;
|
||||
case "inherit":
|
||||
default:
|
||||
return View.TEXT_DIRECTION_INHERIT;
|
||||
}
|
||||
}
|
||||
|
||||
protected PorterDuff.Mode parsePorterDuffMode(@NonNull String mode) {
|
||||
switch (mode) {
|
||||
case "add":
|
||||
return PorterDuff.Mode.ADD;
|
||||
case "multiply":
|
||||
return PorterDuff.Mode.MULTIPLY;
|
||||
case "screen":
|
||||
return PorterDuff.Mode.SCREEN;
|
||||
case "src_atop":
|
||||
return PorterDuff.Mode.SRC_ATOP;
|
||||
case "src_in":
|
||||
return PorterDuff.Mode.SRC_IN;
|
||||
case "src_over":
|
||||
return PorterDuff.Mode.SRC_OVER;
|
||||
default:
|
||||
return PorterDuff.Mode.SRC;
|
||||
}
|
||||
}
|
||||
|
||||
protected float parseFloat(String value) {
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (Throwable th) {
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
protected float parseFloat(String value, float def) {
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (Throwable th) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
protected long parseLong(String value, long def) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (Throwable th) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseDimension(final String value, int defaultValue, final DisplayMetrics dm) {
|
||||
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
char c = value.charAt(0);
|
||||
if (Character.isDigit(c)) {
|
||||
// A dimension value which starts with a digit. E.g.: 1dp, 12sp, 123px, etc.
|
||||
StringBuilder dimensionVal = new StringBuilder();
|
||||
int index = 0;
|
||||
while (index < value.length() && Character.isDigit(c = value.charAt(index))) {
|
||||
dimensionVal.append(c);
|
||||
index++;
|
||||
}
|
||||
|
||||
final int dimen = Integer.parseInt(dimensionVal.toString());
|
||||
final String dimensionType = value.substring(index);
|
||||
return (int) TypedValue.applyDimension(getUnitForDimensionType(dimensionType), dimen, dm);
|
||||
} else if (c == '@') {
|
||||
String name = value.substring("@dimen/".length());
|
||||
return parseDimension(resourceFinder.findDimension(name), defaultValue, dm);
|
||||
} else if (Character.isLetter(c)) {
|
||||
// This could be one of the following :
|
||||
// 1. match_parent
|
||||
// 2. wrap_content
|
||||
// 3. fill_parent
|
||||
switch (value) {
|
||||
case "match_parent":
|
||||
case "fill_parent":
|
||||
return ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
case "wrap_content":
|
||||
default:
|
||||
return ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
|
||||
protected boolean isApi28() {
|
||||
return Build.VERSION.SDK_INT >= 28;
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
|
||||
protected boolean isApi29() {
|
||||
return Build.VERSION.SDK_INT >= 29;
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
|
||||
protected boolean isApi30() {
|
||||
return Build.VERSION.SDK_INT >= 30;
|
||||
}
|
||||
|
||||
private void setRelativeRuleIfTrue(
|
||||
boolean condition, int rule, RelativeLayout.LayoutParams params) {
|
||||
if (condition) {
|
||||
params.addRule(rule);
|
||||
} else {
|
||||
params.removeRule(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.inflater.adapters.android.view;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* An attribute adapter implementation for ViewGroups.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ViewGroupAttrAdapter extends ViewAttrAdapter {
|
||||
|
||||
public ViewGroupAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof ViewGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
|
||||
final ViewGroup group = (ViewGroup) view;
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "animateLayoutChanges":
|
||||
group.setLayoutTransition(new LayoutTransition());
|
||||
break;
|
||||
case "clipChildren":
|
||||
group.setClipChildren(parseBoolean(value));
|
||||
break;
|
||||
case "clipToPadding":
|
||||
group.setClipToPadding(parseBoolean(value));
|
||||
break;
|
||||
case "descendantFocusability":
|
||||
group.setDescendantFocusability(parseDescendantsFocusability(value));
|
||||
break;
|
||||
case "layoutMode":
|
||||
group.setLayoutMode(parseLayoutMode(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private int parseLayoutMode(String value) {
|
||||
switch (value) {
|
||||
case "opticalBounds":
|
||||
return ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS;
|
||||
case "clipBounds":
|
||||
default:
|
||||
return ViewGroup.LAYOUT_MODE_CLIP_BOUNDS;
|
||||
}
|
||||
}
|
||||
|
||||
protected int parseDescendantsFocusability(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "beforeDescendants":
|
||||
return ViewGroup.FOCUS_BEFORE_DESCENDANTS;
|
||||
case "blocksDescendants":
|
||||
return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
|
||||
case "afterDescendants":
|
||||
default:
|
||||
return ViewGroup.FOCUS_AFTER_DESCENDANTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget
|
||||
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.widget.AbsListView
|
||||
import com.itsaky.inflater.IAttribute
|
||||
import com.itsaky.inflater.IResourceTable
|
||||
|
||||
/**
|
||||
* Attribute adapter for the [AbsListView] widget.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class AbsListViewAttrAdapter(resourceTable: IResourceTable, displayMetrics: DisplayMetrics) :
|
||||
AdapterViewAttrAdapter(resourceTable, displayMetrics) {
|
||||
|
||||
override fun isApplicableTo(view: View?): Boolean {
|
||||
return view is AbsListView
|
||||
}
|
||||
|
||||
override fun apply(attribute: IAttribute, view: View): Boolean {
|
||||
val list = view as AbsListView
|
||||
val context = list.context
|
||||
val value = attribute.value
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false
|
||||
}
|
||||
|
||||
var handled = true
|
||||
when (attribute.attributeName) {
|
||||
"cacheColorHint" -> list.cacheColorHint = parseColor(value, context)
|
||||
"choiceMode" -> list.choiceMode = parseChoiceMode(value)
|
||||
"drawSelectorOnTop" -> list.isDrawSelectorOnTop = parseBoolean(value)
|
||||
"fastScrollEnabled" -> list.isFastScrollEnabled = parseBoolean(value)
|
||||
"listSelector" -> list.selector = parseDrawable(value, context)
|
||||
"smoothScrollbar" -> list.isSmoothScrollbarEnabled = parseBoolean(value)
|
||||
"stackFromBottom" -> list.isStackFromBottom = parseBoolean(value)
|
||||
"textFilterEnabled" -> list.isTextFilterEnabled = parseBoolean(value)
|
||||
"transcriptMode" -> list.transcriptMode = parseTranscriptMode(value)
|
||||
else -> handled = false
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view)
|
||||
}
|
||||
|
||||
return handled
|
||||
}
|
||||
|
||||
protected open fun parseTranscriptMode(value: String): Int {
|
||||
return when (value) {
|
||||
"normal" -> AbsListView.TRANSCRIPT_MODE_NORMAL
|
||||
"alwaysScroll" -> AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL
|
||||
else -> AbsListView.TRANSCRIPT_MODE_DISABLED
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun parseChoiceMode(value: String): Int {
|
||||
return when (value) {
|
||||
"multipleChoice" -> AbsListView.CHOICE_MODE_MULTIPLE
|
||||
"multipleChoiceModal" -> AbsListView.CHOICE_MODE_MULTIPLE_MODAL
|
||||
"singleChoice" -> AbsListView.CHOICE_MODE_SINGLE
|
||||
else -> AbsListView.CHOICE_MODE_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.AbsSeekBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/**
|
||||
* Attribute adapter for handling attributes related to AbsSeekBar.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AbsSeekBarAttrAdapter extends ProgressBarAttrAdapter {
|
||||
|
||||
public AbsSeekBarAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof AbsSeekBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final var seek = (AbsSeekBar) view;
|
||||
final var namespace = attribute.getNamespace();
|
||||
final var name = attribute.getAttributeName();
|
||||
final var value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(namespace)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "thumbTint":
|
||||
seek.setThumbTintList(parseColorStateList(value, seek.getContext()));
|
||||
break;
|
||||
case "thumbTintMode":
|
||||
seek.setThumbTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "tickMarkTint":
|
||||
seek.setTickMarkTintList(parseColorStateList(value, seek.getContext()));
|
||||
break;
|
||||
case "tickMarkTintMode":
|
||||
seek.setTickMarkTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.AbsSpinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
public class AbsSpinnerAttrAdapter extends AdapterViewAttrAdapter {
|
||||
|
||||
public AbsSpinnerAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof AbsSpinner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final var spinner = (AbsSpinner) view;
|
||||
final var context = spinner.getContext();
|
||||
final var namespace = attribute.getNamespace();
|
||||
final var name = attribute.getAttributeName();
|
||||
final var value = attribute.getValue();
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
if (!canHandleNamespace(namespace)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "entries":
|
||||
final var array = parseArray(value);
|
||||
final var adapter = newSimpleAdapter(context, array);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
|
||||
/**
|
||||
* Adapter for handling attributes related to AdapterView.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AdapterViewAttrAdapter extends ViewGroupAttrAdapter {
|
||||
|
||||
public AdapterViewAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof AdapterView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
// No special attributes for AdapterView
|
||||
return super.apply(attribute, view);
|
||||
}
|
||||
|
||||
protected ArrayAdapter<String> newSimpleAdapter(Context ctx) {
|
||||
return newSimpleAdapter(ctx, newAdapterItems(4));
|
||||
}
|
||||
|
||||
protected ArrayAdapter<String> newSimpleAdapter(Context ctx, String[] items) {
|
||||
return new ArrayAdapter<String>(ctx, android.R.layout.simple_list_item_1, items);
|
||||
}
|
||||
|
||||
protected String[] newAdapterItems(int size) {
|
||||
final var items = new String[size];
|
||||
for (var i = 0; i < size; i++) {
|
||||
items[i] = "Item " + i;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/**
|
||||
* Adapter for handling attributes related to Button.
|
||||
*
|
||||
* <p>However, {@code android.widget.Button} has no special attributes. This adpater simply inherits
|
||||
* from {@link TextViewAttrAdapter}.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ButtonAttrAdapter extends TextViewAttrAdapter {
|
||||
|
||||
public ButtonAttrAdapter(@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof Button;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
// No special attributes for Button
|
||||
return super.apply(attribute, view);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/** Attribute handler for handling attributes related to CheckBox */
|
||||
public class CheckBoxAttrAdapter extends CompondButtonAttrAdapter {
|
||||
|
||||
public CheckBoxAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof CheckBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
|
||||
// No special attributes for CheckBox
|
||||
return super.apply(attribute, view);
|
||||
}
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.CheckedTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/**
|
||||
* Attribute handler for handling attributes related to CheckedTextView.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class CheckedTextViewAttrAdapter extends TextViewAttrAdapter {
|
||||
|
||||
public CheckedTextViewAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof CheckedTextView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
|
||||
final CheckedTextView text = (CheckedTextView) view;
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "checkMarkTintMode":
|
||||
text.setCheckMarkTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "checkMarkTint":
|
||||
text.setCheckMarkTintList(parseColorStateList(value, text.getContext()));
|
||||
break;
|
||||
case "checkMark":
|
||||
// Ignored...
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/**
|
||||
* Attribute handler for handling attibutes to CompoundButton
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class CompondButtonAttrAdapter extends ButtonAttrAdapter {
|
||||
|
||||
public CompondButtonAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof CompoundButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final CompoundButton button = (CompoundButton) view;
|
||||
final Context context = button.getContext();
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "buttonTint":
|
||||
button.setButtonTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "button":
|
||||
button.setButtonDrawable(parseDrawable(value, context));
|
||||
break;
|
||||
case "buttonTintMode":
|
||||
button.setButtonTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
public class EditTextAttrAdapter extends TextViewAttrAdapter {
|
||||
|
||||
public EditTextAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof EditText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
// No special attributes for EditText
|
||||
return super.apply(attribute, view);
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
|
||||
/**
|
||||
* Attribute adapter for {@link android.widget.FrameLayout}
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class FrameLayoutAttrAdapter extends ViewGroupAttrAdapter {
|
||||
|
||||
public FrameLayoutAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof FrameLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
|
||||
final var frame = (FrameLayout) view;
|
||||
final var name = attribute.getAttributeName();
|
||||
final var value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "foregroundGravity":
|
||||
frame.setForegroundGravity(parseGravity(value));
|
||||
break;
|
||||
case "measureAllChildren":
|
||||
frame.setMeasureAllChildren(parseBoolean(value));
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.GridLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class GridLayoutAttrAdapter extends ViewGroupAttrAdapter {
|
||||
|
||||
public GridLayoutAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof GridLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final var grid = (GridLayout) view;
|
||||
final var name = attribute.getAttributeName();
|
||||
final var value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
switch (name) {
|
||||
case "alignmentMode":
|
||||
grid.setAlignmentMode(parseAlignmentMode(value));
|
||||
break;
|
||||
case "columnCount":
|
||||
grid.setColumnCount(parseInteger(value, Integer.MIN_VALUE));
|
||||
break;
|
||||
case "columnOrderPreserved":
|
||||
grid.setColumnOrderPreserved(parseBoolean(value));
|
||||
break;
|
||||
case "orientation":
|
||||
grid.setOrientation(parseOrientation(value));
|
||||
break;
|
||||
case "rowCount":
|
||||
grid.setRowCount(parseInteger(value, Integer.MIN_VALUE));
|
||||
break;
|
||||
case "rowOrderPreserved":
|
||||
grid.setRowOrderPreserved(parseBoolean(value));
|
||||
break;
|
||||
case "useDefaultMargins":
|
||||
grid.setUseDefaultMargins(parseBoolean(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected int parseOrientation(String value) {
|
||||
if ("vertical".equals(value)) {
|
||||
return GridLayout.VERTICAL;
|
||||
}
|
||||
|
||||
return GridLayout.HORIZONTAL;
|
||||
}
|
||||
|
||||
protected int parseAlignmentMode(String value) {
|
||||
if ("alignBounds".equals(value)) {
|
||||
return GridLayout.ALIGN_BOUNDS;
|
||||
}
|
||||
|
||||
return GridLayout.ALIGN_MARGINS;
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
|
||||
/**
|
||||
* Attribute handler for handling attributes related to ImageButton.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ImageButtonAttrAdapter extends ImageViewAttrAdapter {
|
||||
|
||||
public ImageButtonAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof ImageButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
// No special attributes for ImageButton
|
||||
return super.apply(attribute, view);
|
||||
}
|
||||
}
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter;
|
||||
|
||||
/**
|
||||
* Attribute handler for handling attributes related to ImageButton.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ImageViewAttrAdapter extends ViewAttrAdapter {
|
||||
|
||||
public ImageViewAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof ImageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final ImageView image = (ImageView) view;
|
||||
final Context context = image.getContext();
|
||||
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "adjustViewBounds":
|
||||
image.setAdjustViewBounds(parseBoolean(value));
|
||||
break;
|
||||
case "baseline":
|
||||
image.setBaseline(parseDimension(value, 0, dm));
|
||||
break;
|
||||
case "baselineAlignBottom":
|
||||
image.setBaselineAlignBottom(parseBoolean(value));
|
||||
break;
|
||||
case "cropToPadding":
|
||||
image.setCropToPadding(parseBoolean(value));
|
||||
break;
|
||||
case "maxHeight":
|
||||
image.setMaxHeight(parseDimension(value, 0, dm));
|
||||
break;
|
||||
case "maxWidth":
|
||||
image.setMaxWidth(parseDimension(value, 0, dm));
|
||||
break;
|
||||
case "scaleType":
|
||||
image.setScaleType(parseScaleType(value));
|
||||
break;
|
||||
case "src":
|
||||
image.setImageDrawable(parseImageResource(value, context));
|
||||
break;
|
||||
case "tint":
|
||||
image.setImageTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "tintMode":
|
||||
image.setImageTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private Drawable parseImageResource(String value, Context context) {
|
||||
if (value.startsWith("@drawable/") || value.startsWith("@mipmap/")) {
|
||||
return parseDrawable(value, context);
|
||||
}
|
||||
return ContextCompat.getDrawable(context, android.R.drawable.ic_delete);
|
||||
}
|
||||
|
||||
private ImageView.ScaleType parseScaleType(String value) {
|
||||
switch (value) {
|
||||
case "center":
|
||||
return ImageView.ScaleType.CENTER;
|
||||
case "centerCrop":
|
||||
return ImageView.ScaleType.CENTER_CROP;
|
||||
case "centerInside":
|
||||
return ImageView.ScaleType.CENTER_INSIDE;
|
||||
case "fitEnd":
|
||||
return ImageView.ScaleType.FIT_END;
|
||||
case "fitStart":
|
||||
return ImageView.ScaleType.FIT_START;
|
||||
case "fitXY":
|
||||
return ImageView.ScaleType.FIT_XY;
|
||||
case "matrix":
|
||||
return ImageView.ScaleType.MATRIX;
|
||||
case "fitCenter":
|
||||
default:
|
||||
return ImageView.ScaleType.FIT_CENTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
|
||||
/**
|
||||
* Adapter for handling attributes related to LinearLayout.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class LinearLayoutAttrAdapter extends ViewGroupAttrAdapter {
|
||||
|
||||
public LinearLayoutAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof LinearLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final LinearLayout linear = (LinearLayout) view;
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "baselineAligned":
|
||||
linear.setBaselineAligned(parseBoolean(value));
|
||||
break;
|
||||
case "baselineAlignedChildIndex":
|
||||
linear.setBaselineAlignedChildIndex(parseInteger(value, linear.getChildCount()));
|
||||
break;
|
||||
case "gravity":
|
||||
linear.setGravity(parseGravity(value));
|
||||
break;
|
||||
case "measureWithLargestChild":
|
||||
linear.setMeasureWithLargestChildEnabled(parseBoolean(value));
|
||||
break;
|
||||
case "orientation":
|
||||
linear.setOrientation(parseOrientation(value));
|
||||
break;
|
||||
case "weightSum":
|
||||
linear.setWeightSum(parseFloat(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected int parseOrientation(@NonNull String value) {
|
||||
switch (value) {
|
||||
case "vertical":
|
||||
return LinearLayout.VERTICAL;
|
||||
case "horizontal":
|
||||
default:
|
||||
return LinearLayout.HORIZONTAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget
|
||||
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
import com.itsaky.inflater.IAttribute
|
||||
import com.itsaky.inflater.IResourceTable
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class ListViewAttrAdapter(resourceTable: IResourceTable, displayMetrics: DisplayMetrics) :
|
||||
AbsListViewAttrAdapter(resourceTable, displayMetrics) {
|
||||
|
||||
override fun isApplicableTo(view: View?): Boolean {
|
||||
return view is ListView
|
||||
}
|
||||
|
||||
override fun apply(attribute: IAttribute, view: View): Boolean {
|
||||
val list = view as ListView
|
||||
val value = attribute.value
|
||||
val context = list.context
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false
|
||||
}
|
||||
|
||||
var handled = true
|
||||
|
||||
when (attribute.attributeName) {
|
||||
"divider" -> list.divider = parseDrawable(value, context)
|
||||
"dividerHeight" ->
|
||||
list.dividerHeight = parseDimension(value, SizeUtils.dp2px(1f), displayMetrics)
|
||||
"entries" -> {
|
||||
val entries = parseArray(value)
|
||||
list.adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, entries)
|
||||
}
|
||||
"footerDividersEnabled" -> list.setFooterDividersEnabled(parseBoolean(value))
|
||||
"headerDividersEnabled" -> list.setHeaderDividersEnabled(parseBoolean(value))
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view)
|
||||
}
|
||||
|
||||
return handled
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.inflater.adapters.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter;
|
||||
|
||||
/**
|
||||
* Attribute adapter for handling attributes related to ProgressBar.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ProgressBarAttrAdapter extends ViewAttrAdapter {
|
||||
|
||||
public ProgressBarAttrAdapter(
|
||||
@NonNull IResourceTable resourceFinder, DisplayMetrics displayMetrics) {
|
||||
super(resourceFinder, displayMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(View view) {
|
||||
return view instanceof ProgressBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(@NonNull IAttribute attribute, @NonNull View view) {
|
||||
final ProgressBar pb = (ProgressBar) view;
|
||||
final Context context = pb.getContext();
|
||||
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
|
||||
if (!canHandleNamespace(attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
|
||||
switch (name) {
|
||||
case "indeterminate":
|
||||
pb.setIndeterminate(parseBoolean(value));
|
||||
break;
|
||||
case "indeterminateDrawable":
|
||||
pb.setIndeterminateDrawable(parseDrawable(value, context));
|
||||
break;
|
||||
case "indeterminateTint":
|
||||
pb.setIndeterminateTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "indeterminateTintMode":
|
||||
pb.setIndeterminateTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "max":
|
||||
pb.setMax(parseInteger(value, 100));
|
||||
break;
|
||||
case "maxHeight":
|
||||
if (isApi29()) {
|
||||
pb.setMaxHeight(parseDimension(value, Integer.MAX_VALUE, dm));
|
||||
}
|
||||
break;
|
||||
case "maxWidth":
|
||||
if (isApi29()) {
|
||||
pb.setMaxWidth(parseDimension(value, Integer.MAX_VALUE, dm));
|
||||
}
|
||||
break;
|
||||
case "min":
|
||||
pb.setMin(parseInteger(value, 0));
|
||||
break;
|
||||
case "minHeight":
|
||||
if (isApi29()) {
|
||||
pb.setMinHeight(parseDimension(value, 0, dm));
|
||||
}
|
||||
break;
|
||||
case "minWidth":
|
||||
if (isApi29()) {
|
||||
pb.setMinWidth(parseDimension(value, 0, dm));
|
||||
}
|
||||
break;
|
||||
case "progress":
|
||||
pb.setProgress(parseInteger(value, 50));
|
||||
break;
|
||||
case "progressBackgroundTint":
|
||||
pb.setProgressBackgroundTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "progressBackgroundTintMode":
|
||||
pb.setProgressBackgroundTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "progressDrawable":
|
||||
pb.setProgressDrawable(parseDrawable(value, context));
|
||||
break;
|
||||
case "progressTint":
|
||||
pb.setProgressTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "progressTintMode":
|
||||
pb.setProgressTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
case "secondaryProgress":
|
||||
pb.setSecondaryProgress(parseInteger(value, 0));
|
||||
break;
|
||||
case "secondaryProgressTint":
|
||||
pb.setSecondaryProgressTintList(parseColorStateList(value, context));
|
||||
break;
|
||||
case "secondaryProgressTintMode":
|
||||
pb.setSecondaryProgressTintMode(parsePorterDuffMode(value));
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = super.apply(attribute, view);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user