feat: MyTermuxActivity 重构为 TermuxButton 列表管理界面

- models/TermuxButtonManager: 新增数据管理层,支持 CRUD 及 JSON 文件持久化
- MyTermuxActivity: 替换硬编码按钮为 ListView,支持点击执行、长按编辑/删除
- layout/activity_my_termux: 布局重构,替换 ScrollView 为 ListView + 添加按钮
- strings.xml: 新增 17 条中文字符串资源
This commit is contained in:
LaizyBoy
2026-05-13 11:28:09 +08:00
parent ee75f95c79
commit bbb0e22198
6 changed files with 269 additions and 110 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 13 10:28:36 HKT 2026
#Wed May 13 11:15:04 CST 2026
stageCount=2
libraryProject=libwinboll
baseVersion=15.20
publishVersion=15.20.1
buildCount=0
buildCount=1
baseBetaVersion=15.20.2

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 13 10:28:36 HKT 2026
#Wed May 13 11:15:04 CST 2026
stageCount=2
libraryProject=libwinboll
baseVersion=15.20
publishVersion=15.20.1
buildCount=0
buildCount=1
baseBetaVersion=15.20.2

View File

@@ -1,31 +1,46 @@
package cc.winboll.studio.winboll.applications;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.models.TermuxButtonManager;
import cc.winboll.studio.winboll.models.TermuxButtonModel;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
import java.util.ArrayList;
public class MyTermuxActivity extends AppCompatActivity {
public static final String TAG = "MyTermuxActivity";
private Toolbar mToolbar;
private Button mTermuxButton;
private Button mTermuxWorkSpacesButton;
private ListView mListView;
private Button mBtnAdd;
private ButtonAdapter mAdapter;
private ArrayList<TermuxButtonModel> mButtonList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_termux);
// 初始化工具栏
initToolbar();
// 初始化按钮
initView();
initListView();
initAddButton();
refreshList();
}
private void initToolbar() {
@@ -34,44 +49,189 @@ public class MyTermuxActivity extends AppCompatActivity {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击返回按钮");
finish();
}
});
LogUtils.d(TAG, "工具栏初始化完成");
@Override
public void onClick(View v) {
finish();
}
});
}
}
private void initView() {
mTermuxButton = findViewById(R.id.btn_termux);
if (mTermuxButton != null) {
mTermuxButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 Termux 按钮");
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~");
//TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~/TermuxWorkSpaces", "./TermuxWorkSpaces");
}
});
LogUtils.d(TAG, "Termux 按钮初始化完成");
}
private void initListView() {
mListView = findViewById(R.id.list_termux_buttons);
mButtonList = new ArrayList<TermuxButtonModel>();
mAdapter = new ButtonAdapter();
mListView.setAdapter(mAdapter);
mTermuxWorkSpacesButton = findViewById(R.id.btn_termuxworkspaces);
if (mTermuxWorkSpacesButton != null) {
mTermuxWorkSpacesButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 TermuxWorkSpaces 按钮");
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~/TermuxWorkSpaces", "./TermuxWorkSpaces");
}
});
LogUtils.d(TAG, "TermuxWorkSpaces 按钮初始化完成");
}
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TermuxButtonModel model = mButtonList.get(position);
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getExeCommand(), model.getWorkDir());
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
showContextMenu(position);
return true;
}
});
}
private boolean isTermuxAvailable() {
return TermuxCommandExecutor.isTermuxInstalled(this);
private void initAddButton() {
mBtnAdd = findViewById(R.id.btn_add_termux_button);
mBtnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showButtonDialog(-1, null);
}
});
}
private void refreshList() {
mButtonList.clear();
ArrayList<TermuxButtonModel> loaded = TermuxButtonManager.loadButtons(this);
if (loaded != null) {
mButtonList.addAll(loaded);
}
mAdapter.notifyDataSetChanged();
}
private void showContextMenu(final int position) {
final TermuxButtonModel model = mButtonList.get(position);
String[] items = new String[]{
getString(R.string.menu_execute),
getString(R.string.menu_edit),
getString(R.string.menu_delete),
getString(R.string.menu_cancel)
};
new AlertDialog.Builder(this)
.setTitle(model.getButtonName())
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
model.getExeCommand(), model.getWorkDir());
} else if (which == 1) {
showButtonDialog(position, model);
} else if (which == 2) {
showDeleteConfirmDialog(position);
}
}
})
.show();
}
private void showDeleteConfirmDialog(final int position) {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.dialog_delete_title))
.setMessage(getString(R.string.dialog_delete_message) + mButtonList.get(position).getButtonName())
.setPositiveButton(getString(R.string.dialog_confirm), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxButtonManager.deleteButton(MyTermuxActivity.this, mButtonList, position);
refreshList();
Toast.makeText(MyTermuxActivity.this, R.string.toast_deleted, Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
private void showButtonDialog(final int index, final TermuxButtonModel model) {
final boolean isEdit = (model != null);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(40, 20, 40, 20);
final EditText etName = new EditText(this);
etName.setHint(R.string.hint_button_name);
if (model != null) {
etName.setText(model.getButtonName());
}
layout.addView(etName);
final EditText etCommand = new EditText(this);
etCommand.setHint(R.string.hint_exe_command);
if (model != null) {
etCommand.setText(model.getExeCommand());
}
layout.addView(etCommand);
final EditText etWorkDir = new EditText(this);
etWorkDir.setHint(R.string.hint_work_dir);
if (model != null) {
etWorkDir.setText(model.getWorkDir());
}
layout.addView(etWorkDir);
int titleResId = isEdit ? R.string.dialog_edit_title : R.string.dialog_add_title;
new AlertDialog.Builder(this)
.setTitle(titleResId)
.setView(layout)
.setPositiveButton(getString(R.string.dialog_save), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = etName.getText().toString().trim();
String command = etCommand.getText().toString().trim();
String workDir = etWorkDir.getText().toString().trim();
if (name.isEmpty() || command.isEmpty()) {
Toast.makeText(MyTermuxActivity.this, R.string.toast_fields_required, Toast.LENGTH_SHORT).show();
return;
}
TermuxButtonModel newModel = new TermuxButtonModel();
newModel.setButtonName(name);
newModel.setExeCommand(command);
newModel.setWorkDir(workDir);
if (isEdit) {
TermuxButtonManager.updateButton(MyTermuxActivity.this, mButtonList, index, newModel);
} else {
TermuxButtonManager.addButton(MyTermuxActivity.this, mButtonList, newModel);
}
refreshList();
}
})
.setNegativeButton(getString(R.string.dialog_cancel), null)
.show();
}
private class ButtonAdapter extends BaseAdapter {
@Override
public int getCount() {
return mButtonList.size();
}
@Override
public Object getItem(int position) {
return mButtonList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if (convertView == null) {
tv = new TextView(MyTermuxActivity.this);
tv.setPadding(30, 20, 30, 20);
tv.setTextSize(16);
tv.setMinHeight(80);
} else {
tv = (TextView) convertView;
}
TermuxButtonModel model = mButtonList.get(position);
tv.setText(model.getButtonName() + "\n" + model.getExeCommand());
tv.setTextColor(getResources().getColor(android.R.color.white));
return tv;
}
}
}

View File

@@ -0,0 +1,37 @@
package cc.winboll.studio.winboll.models;
import android.content.Context;
import cc.winboll.studio.libappbase.BaseBean;
import java.util.ArrayList;
public class TermuxButtonManager {
public static ArrayList<TermuxButtonModel> loadButtons(Context context) {
ArrayList<TermuxButtonModel> list = new ArrayList<TermuxButtonModel>();
BaseBean.loadBeanList(context, list, TermuxButtonModel.class);
return list;
}
public static boolean saveButtons(Context context, ArrayList<TermuxButtonModel> list) {
return BaseBean.saveBeanList(context, list, TermuxButtonModel.class);
}
public static void addButton(Context context, ArrayList<TermuxButtonModel> list, TermuxButtonModel button) {
list.add(button);
saveButtons(context, list);
}
public static void updateButton(Context context, ArrayList<TermuxButtonModel> list, int index, TermuxButtonModel button) {
if (index >= 0 && index < list.size()) {
list.set(index, button);
saveButtons(context, list);
}
}
public static void deleteButton(Context context, ArrayList<TermuxButtonModel> list, int index) {
if (index >= 0 && index < list.size()) {
list.remove(index);
saveButtons(context, list);
}
}
}

View File

@@ -4,8 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top">
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
@@ -17,74 +16,20 @@
app:titleTextColor="@android:color/white"
app:subtitleTextColor="@android:color/white"/>
<ScrollView
<ListView
android:id="@+id/list_termux_buttons"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
android:layout_weight="1.0"
android:divider="@android:color/darker_gray"
android:dividerHeight="1dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<cc.winboll.studio.winboll.views.TermuxButton
android:id="@+id/btn_termux"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Termux"
android:textSize="18sp"
android:padding="16dp"
android:backgroundTint="@android:color/holo_blue_dark"
app:exeCommand="cd ~"
app:workDir="~"
app:isCommitted="true"
app:commitTitle="打开 Termux"
app:commitInfo="打开 Termux 应用"/>
<cc.winboll.studio.winboll.views.TermuxButton
android:id="@+id/btn_termuxworkspaces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TermuxWorkSpaces"
android:textSize="18sp"
android:padding="16dp"
android:backgroundTint="@android:color/holo_blue_dark"
app:exeCommand="cd ~/TermuxWorkSpaces"
app:workDir="~"
app:isCommitted="false"
app:commitTitle="打开 TermuxWorkSpaces"
app:commitInfo="打开 Termux 应用,进入 TermuxWorkSpaces 目录。"/>
</LinearLayout>
</HorizontalScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:id="@+id/btn_addtermuxbutton"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/btn_add_termux_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_termux_button"
android:textSize="16sp"
android:padding="12dp"/>
</LinearLayout>

View File

@@ -13,4 +13,21 @@
<string name="toolbar_icon_description">WinBoLL APP</string>
<string name="my_termux_activity">MyTermuxActivity</string>
<string name="pattern_lock_title">图案密码设置</string>
<string name="add_termux_button">+ 添加Termux按钮</string>
<string name="menu_execute">执行</string>
<string name="menu_edit">编辑</string>
<string name="menu_delete">删除</string>
<string name="menu_cancel">取消</string>
<string name="dialog_delete_title">确认删除</string>
<string name="dialog_delete_message">确定要删除</string>
<string name="dialog_add_title">添加Termux按钮</string>
<string name="dialog_edit_title">编辑Termux按钮</string>
<string name="dialog_save">保存</string>
<string name="dialog_confirm">确定</string>
<string name="dialog_cancel">取消</string>
<string name="hint_button_name">按钮名称</string>
<string name="hint_exe_command">执行命令</string>
<string name="hint_work_dir">工作目录(默认 ~</string>
<string name="toast_deleted">已删除</string>
<string name="toast_fields_required">按钮名称和执行命令不能为空</string>
</resources>