mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat: implement UI for new templates API
This commit is contained in:
@@ -6,6 +6,7 @@ plugins {
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
id("com.google.android.gms.oss-licenses-plugin")
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -101,11 +102,15 @@ dependencies {
|
||||
implementation(libs.androidx.coordinatorlayout)
|
||||
implementation(libs.androidx.drawer)
|
||||
implementation(libs.androidx.grid)
|
||||
implementation(libs.androidx.nav.fragment)
|
||||
implementation(libs.androidx.nav.ui)
|
||||
implementation(libs.androidx.preference)
|
||||
implementation(libs.androidx.recyclerview)
|
||||
implementation(libs.androidx.transition)
|
||||
implementation(libs.androidx.vectors)
|
||||
implementation(libs.androidx.animated.vectors)
|
||||
implementation(libs.google.material)
|
||||
implementation(libs.google.flexbox)
|
||||
implementation(libs.google.oss.licenses)
|
||||
|
||||
// Kotlin
|
||||
@@ -136,6 +141,8 @@ dependencies {
|
||||
implementation(projects.preferences)
|
||||
implementation(projects.resources)
|
||||
implementation(projects.treeview)
|
||||
implementation(projects.templatesApi)
|
||||
implementation(projects.templatesImpl)
|
||||
implementation(projects.uidesigner)
|
||||
implementation(projects.xmlInflater)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
android:name=".app.IDEApplication"
|
||||
android:allowBackup="false"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -76,7 +77,7 @@
|
||||
android:exported="true"
|
||||
android:permission="com.itsaky.androidide.permission.BIND_LOG_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="com.itsaky.androidide.LOG_SERVICE_ACTION"/>
|
||||
<action android:name="com.itsaky.androidide.LOG_SERVICE_ACTION" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -16,23 +16,26 @@
|
||||
*/
|
||||
package com.itsaky.androidide.activities
|
||||
|
||||
import android.R
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import androidx.transition.doOnEnd
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.itsaky.androidide.R.id
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import com.itsaky.androidide.activities.editor.EditorActivityKt
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.app.IDEActivity
|
||||
import com.itsaky.androidide.databinding.ActivityMainBinding
|
||||
import com.itsaky.androidide.fragments.MainFragment
|
||||
import com.itsaky.androidide.preferences.internal.NO_OPENED_PROJECT
|
||||
import com.itsaky.androidide.preferences.internal.autoOpenProjects
|
||||
import com.itsaky.androidide.preferences.internal.confirmProjectOpen
|
||||
@@ -43,28 +46,106 @@ import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel.Companion.SCREEN_MAIN
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel.Companion.SCREEN_TEMPLATE_DETAILS
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel.Companion.SCREEN_TEMPLATE_LIST
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : IDEActivity() {
|
||||
|
||||
private var binding: ActivityMainBinding? = null
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
private var _binding: ActivityMainBinding? = null
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
viewModel.apply {
|
||||
val newScreen = when (currentScreen.value) {
|
||||
SCREEN_TEMPLATE_DETAILS -> SCREEN_TEMPLATE_LIST
|
||||
SCREEN_TEMPLATE_LIST -> SCREEN_MAIN
|
||||
else -> SCREEN_MAIN
|
||||
}
|
||||
|
||||
if (currentScreen.value != newScreen) {
|
||||
setScreen(newScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val binding: ActivityMainBinding
|
||||
get() = checkNotNull(_binding)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (!BaseApplication.isAbiSupported()) {
|
||||
showDeviceNotSupported()
|
||||
return
|
||||
}
|
||||
|
||||
if (!checkToolsIsInstalled()) {
|
||||
showDialogInstallJdkSdk()
|
||||
} else {
|
||||
openLastProject()
|
||||
}
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(id.container, MainFragment(), MainFragment.TAG)
|
||||
.commit()
|
||||
|
||||
viewModel.currentScreen.observe(this) { screen ->
|
||||
if (screen == -1) {
|
||||
return@observe
|
||||
}
|
||||
|
||||
try {
|
||||
onScreenChanged(screen)
|
||||
} catch (err: Exception) {
|
||||
onBackPressedCallback.isEnabled = screen != SCREEN_MAIN
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.setScreen(SCREEN_MAIN)
|
||||
|
||||
onBackPressedDispatcher.addCallback(/* owner = */
|
||||
this, /* onBackPressedCallback = */ onBackPressedCallback)
|
||||
}
|
||||
|
||||
private fun onScreenChanged(screen: Int?) {
|
||||
val previous = viewModel.previousScreen
|
||||
if (previous != -1) {
|
||||
val axis =
|
||||
if ((previous == SCREEN_TEMPLATE_LIST || previous == SCREEN_TEMPLATE_DETAILS) && (screen == SCREEN_TEMPLATE_LIST || screen == SCREEN_TEMPLATE_DETAILS)) {
|
||||
MaterialSharedAxis.X
|
||||
} else MaterialSharedAxis.Y
|
||||
|
||||
val isForward = when {
|
||||
previous == SCREEN_MAIN && screen == SCREEN_TEMPLATE_LIST -> true
|
||||
previous == SCREEN_TEMPLATE_LIST && screen == SCREEN_TEMPLATE_DETAILS -> true
|
||||
previous == SCREEN_TEMPLATE_DETAILS && screen == SCREEN_TEMPLATE_LIST -> false
|
||||
previous == SCREEN_TEMPLATE_LIST && screen == SCREEN_MAIN -> false
|
||||
else -> throw IllegalStateException("Invalid screen states")
|
||||
}
|
||||
|
||||
val transition = MaterialSharedAxis(axis, isForward)
|
||||
transition.doOnEnd {
|
||||
onBackPressedCallback.isEnabled =
|
||||
viewModel.currentScreen.value != SCREEN_MAIN
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.root, transition)
|
||||
}
|
||||
|
||||
val view = when (screen) {
|
||||
SCREEN_MAIN -> binding.main
|
||||
SCREEN_TEMPLATE_LIST -> binding.templateList
|
||||
SCREEN_TEMPLATE_DETAILS -> binding.templateDetails
|
||||
else -> throw IllegalArgumentException("Invalid screen id: '$screen'")
|
||||
}
|
||||
|
||||
for (fragment in arrayOf(binding.main, binding.templateList,
|
||||
binding.templateDetails)) {
|
||||
fragment.isVisible = fragment == view
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStorageGranted() {}
|
||||
override fun onStorageDenied() {
|
||||
flashError(string.msg_storage_denied)
|
||||
finishAffinity()
|
||||
@@ -78,8 +159,8 @@ class MainActivity : IDEActivity() {
|
||||
}
|
||||
|
||||
override fun bindLayout(): View {
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
return binding!!.root
|
||||
_binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun showDialogInstallJdkSdk() {
|
||||
@@ -94,8 +175,9 @@ class MainActivity : IDEActivity() {
|
||||
view.movementMethod = LinkMovementMethod.getInstance()
|
||||
builder.setView(view)
|
||||
builder.setCancelable(false)
|
||||
builder.setPositiveButton(R.string.ok) { _, _ -> openTerminal() }
|
||||
builder.setNegativeButton(R.string.cancel) { _, _ -> finishAffinity() }
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> openTerminal() }
|
||||
builder.setNegativeButton(
|
||||
android.R.string.cancel) { _, _ -> finishAffinity() }
|
||||
builder.setNeutralButton(string.btn_docs) { _, _ -> app.openDocs() }
|
||||
builder.show()
|
||||
}
|
||||
@@ -109,36 +191,41 @@ class MainActivity : IDEActivity() {
|
||||
builder.setTitle(string.title_device_not_supported)
|
||||
builder.setMessage(string.msg_device_not_supported)
|
||||
builder.setCancelable(false)
|
||||
builder.setPositiveButton(R.string.ok) { _, _ -> finishAffinity() }
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> finishAffinity() }
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
private fun openLastProject() {
|
||||
binding!!.root.post { tryOpenLastProject() }
|
||||
binding.root.post { tryOpenLastProject() }
|
||||
}
|
||||
|
||||
private fun tryOpenLastProject() {
|
||||
if (!autoOpenProjects) {
|
||||
return
|
||||
}
|
||||
|
||||
val openedProject = lastOpenedProject
|
||||
if (NO_OPENED_PROJECT == openedProject) {
|
||||
return
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(openedProject)) {
|
||||
app
|
||||
flashInfo(string.msg_opened_project_does_not_exist)
|
||||
return
|
||||
}
|
||||
|
||||
val project = File(openedProject)
|
||||
if (!project.exists()) {
|
||||
flashInfo(string.msg_opened_project_does_not_exist)
|
||||
return
|
||||
}
|
||||
|
||||
if (confirmProjectOpen) {
|
||||
askProjectOpenPermission(project)
|
||||
return
|
||||
}
|
||||
|
||||
openProject(project)
|
||||
}
|
||||
|
||||
@@ -164,6 +251,6 @@ class MainActivity : IDEActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.blankj.utilcode.util.ConvertUtils
|
||||
import com.google.android.flexbox.FlexboxLayoutManager
|
||||
import com.google.android.material.shape.CornerFamily
|
||||
import com.itsaky.androidide.adapters.TemplateListAdapter.ViewHolder
|
||||
import com.itsaky.androidide.databinding.LayoutTemplateListItemBinding
|
||||
import com.itsaky.androidide.templates.Template
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] for showing templates in a [RecyclerView].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TemplateListAdapter(templates: List<Template>, private val onClick: ((Template, ViewHolder) -> Unit)? = null
|
||||
) : RecyclerView.Adapter<ViewHolder>() {
|
||||
|
||||
private val templates = templates.toMutableList()
|
||||
|
||||
class ViewHolder(internal val binding: LayoutTemplateListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int
|
||||
): ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutTemplateListItemBinding.inflate(LayoutInflater.from(parent.context),
|
||||
parent, false))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return templates.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.binding.apply {
|
||||
val template = templates[position]
|
||||
if (template == Template.EMPTY) {
|
||||
root.visibility = View.INVISIBLE
|
||||
return@apply
|
||||
}
|
||||
templateName.setText(template.templateName)
|
||||
templateIcon.setImageResource(template.thumb)
|
||||
templateIcon.shapeAppearanceModel =
|
||||
templateIcon.shapeAppearanceModel.toBuilder()
|
||||
.setAllCorners(CornerFamily.ROUNDED, ConvertUtils.dp2px(8f).toFloat())
|
||||
.build()
|
||||
|
||||
root.setOnClickListener {
|
||||
onClick?.invoke(template, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun fillDiff(extras: Int) {
|
||||
val count = itemCount
|
||||
for (i in 1..extras) {
|
||||
templates.add(Template.EMPTY)
|
||||
}
|
||||
|
||||
val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
override fun getOldListSize(): Int {
|
||||
return count
|
||||
}
|
||||
|
||||
override fun getNewListSize(): Int {
|
||||
return count + extras
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int
|
||||
): Boolean {
|
||||
return newItemPosition < count && oldItemPosition == newItemPosition
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int
|
||||
): Boolean {
|
||||
return areItemsTheSame(oldItemPosition, newItemPosition)
|
||||
}
|
||||
})
|
||||
|
||||
diff.dispatchUpdatesTo(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.itsaky.androidide.adapters.TemplateWidgetsListAdapter.WidgetViewHolder
|
||||
import com.itsaky.androidide.databinding.LayoutTemplateWidgetlistItemBinding
|
||||
import com.itsaky.androidide.templates.ITemplateWidgetViewProvider
|
||||
import com.itsaky.androidide.templates.Widget
|
||||
|
||||
/**
|
||||
* A [RecyclerView.Adapter] that is used to show the widgets from templates.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TemplateWidgetsListAdapter(private val widgets: List<Widget<*>>) :
|
||||
RecyclerView.Adapter<WidgetViewHolder>() {
|
||||
|
||||
class WidgetViewHolder(
|
||||
internal val binding: LayoutTemplateWidgetlistItemBinding
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int
|
||||
): WidgetViewHolder {
|
||||
return WidgetViewHolder(LayoutTemplateWidgetlistItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return widgets.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WidgetViewHolder, position: Int) {
|
||||
holder.binding.apply {
|
||||
val viewProvider = ITemplateWidgetViewProvider.getInstance()
|
||||
val widget = widgets[position]
|
||||
val view = viewProvider.createView(root.context, widget)
|
||||
|
||||
root.removeAllViews()
|
||||
root.addView(view,
|
||||
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,14 @@ package com.itsaky.androidide.adapters;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.blankj.utilcode.util.ConvertUtils;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.shape.CornerFamily;
|
||||
import com.itsaky.androidide.databinding.WizardTemplateItemBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutTemplateListItemBinding;
|
||||
import com.itsaky.androidide.models.ProjectTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -23,16 +20,15 @@ public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAd
|
||||
private final List<ProjectTemplate> mItems = new ArrayList<>();
|
||||
private OnItemClickListener mListener;
|
||||
|
||||
public WizardTemplateAdapter() {}
|
||||
public WizardTemplateAdapter() {
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void submitList(@NonNull List<ProjectTemplate> newItems) {
|
||||
DiffUtil.DiffResult diffResult =
|
||||
DiffUtil.calculateDiff(
|
||||
new DiffUtil.Callback() {
|
||||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return mItems.size();
|
||||
@@ -61,14 +57,11 @@ public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAd
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
WizardTemplateItemBinding binding =
|
||||
WizardTemplateItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
LayoutTemplateListItemBinding binding = LayoutTemplateListItemBinding.inflate(
|
||||
LayoutInflater.from(parent.getContext()), parent, false);
|
||||
|
||||
ViewHolder holder = new ViewHolder(binding);
|
||||
binding
|
||||
.getRoot()
|
||||
.setOnClickListener(
|
||||
view1 -> {
|
||||
binding.getRoot().setOnClickListener(view1 -> {
|
||||
if (mListener != null) {
|
||||
int pos = holder.getBindingAdapterPosition();
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
@@ -94,7 +87,7 @@ public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAd
|
||||
public final ShapeableImageView icon;
|
||||
public final TextView name;
|
||||
|
||||
public ViewHolder(WizardTemplateItemBinding view) {
|
||||
public ViewHolder(LayoutTemplateListItemBinding view) {
|
||||
super(view.getRoot());
|
||||
|
||||
icon = view.templateIcon;
|
||||
@@ -104,14 +97,15 @@ public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAd
|
||||
private void bind(ProjectTemplate template) {
|
||||
name.setText(template.getName());
|
||||
icon.setImageResource(template.getImageId());
|
||||
icon.setShapeAppearanceModel(
|
||||
icon.getShapeAppearanceModel().toBuilder()
|
||||
icon.setShapeAppearanceModel(icon.getShapeAppearanceModel()
|
||||
.toBuilder()
|
||||
.setAllCorners(CornerFamily.ROUNDED, ConvertUtils.dp2px(8))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
|
||||
void onItemClick(ProjectTemplate item, int position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.itsaky.androidide.activities.PreferencesActivity
|
||||
@@ -27,18 +28,20 @@ import com.itsaky.androidide.utils.Environment
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.flashSuccess
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ProgressMonitor
|
||||
import java.io.File
|
||||
|
||||
class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
|
||||
private val viewModel by viewModels<MainViewModel>(
|
||||
ownerProducer = { requireActivity() })
|
||||
private var binding: FragmentMainBinding? = null
|
||||
|
||||
private val log = ILogger.newInstance("MainFragment")
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentMainBinding.inflate(inflater, container, false)
|
||||
@@ -47,39 +50,28 @@ class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val createProject =
|
||||
MainScreenAction(string.create_project, R.drawable.ic_add) { showCreateProject() }
|
||||
val openProject =
|
||||
MainScreenAction(string.msg_open_existing_project, R.drawable.ic_folder) { pickDirectory() }
|
||||
val cloneGitRepository =
|
||||
MainScreenAction(string.git_clone_repo, R.drawable.ic_git) { cloneGitRepo() }
|
||||
val createProject = MainScreenAction(string.create_project,
|
||||
R.drawable.ic_add) { showCreateProject() }
|
||||
val openProject = MainScreenAction(string.msg_open_existing_project,
|
||||
R.drawable.ic_folder) { pickDirectory() }
|
||||
val cloneGitRepository = MainScreenAction(string.git_clone_repo,
|
||||
R.drawable.ic_git) { cloneGitRepo() }
|
||||
val openTerminal =
|
||||
MainScreenAction(string.title_terminal, R.drawable.ic_terminal) {
|
||||
startActivity(Intent(requireActivity(), TerminalActivity::class.java))
|
||||
}
|
||||
val preferences =
|
||||
MainScreenAction(string.msg_preferences, R.drawable.ic_settings) { gotoPreferences() }
|
||||
val sponsor =
|
||||
MainScreenAction(string.btn_donate, R.drawable.ic_donate) {
|
||||
val preferences = MainScreenAction(string.msg_preferences,
|
||||
R.drawable.ic_settings) { gotoPreferences() }
|
||||
val sponsor = MainScreenAction(string.btn_donate, R.drawable.ic_donate) {
|
||||
BaseApplication.getBaseInstance().openSponsors()
|
||||
}
|
||||
val docs =
|
||||
MainScreenAction(string.btn_docs, R.drawable.ic_docs) {
|
||||
val docs = MainScreenAction(string.btn_docs, R.drawable.ic_docs) {
|
||||
BaseApplication.getBaseInstance().openDocs()
|
||||
}
|
||||
|
||||
binding!!.actions.adapter =
|
||||
MainActionsListAdapter(
|
||||
listOf(
|
||||
createProject,
|
||||
openProject,
|
||||
cloneGitRepository,
|
||||
openTerminal,
|
||||
preferences,
|
||||
docs,
|
||||
sponsor
|
||||
)
|
||||
)
|
||||
binding!!.actions.adapter = MainActionsListAdapter(
|
||||
listOf(createProject, openProject, cloneGitRepository, openTerminal,
|
||||
preferences, docs, sponsor))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -92,13 +84,7 @@ class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
}
|
||||
|
||||
private fun showCreateProject() {
|
||||
val wizardFragment = WizardFragment()
|
||||
wizardFragment.setOnProjectCreatedListener(this)
|
||||
parentFragmentManager
|
||||
.beginTransaction()
|
||||
.add(com.itsaky.androidide.R.id.container, wizardFragment, WizardFragment.TAG)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_LIST)
|
||||
}
|
||||
|
||||
override fun openProject(root: File) {
|
||||
@@ -149,18 +135,14 @@ class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
|
||||
val progress = GitCloneProgressMonitor(binding.progress, binding.message)
|
||||
var git: Git? = null
|
||||
val future =
|
||||
executeAsyncProvideError(
|
||||
{
|
||||
val future = executeAsyncProvideError({
|
||||
return@executeAsyncProvideError Git.cloneRepository()
|
||||
.setURI(url)
|
||||
.setDirectory(targetDir)
|
||||
.setProgressMonitor(progress)
|
||||
.call()
|
||||
.also { git = it }
|
||||
},
|
||||
{ _, _ -> }
|
||||
)
|
||||
}, { _, _ -> })
|
||||
|
||||
builder.setPositiveButton(android.R.string.cancel) { iface, _ ->
|
||||
iface.dismiss()
|
||||
@@ -201,13 +183,10 @@ class MainFragment : BaseFragment(), OnProjectCreatedListener {
|
||||
startActivity(Intent(requireActivity(), PreferencesActivity::class.java))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MainFragmentTag"
|
||||
}
|
||||
|
||||
// TODO(itsaky) : Improve this implementation
|
||||
class GitCloneProgressMonitor(val progress: LinearProgressIndicator, val message: TextView) :
|
||||
ProgressMonitor {
|
||||
class GitCloneProgressMonitor(val progress: LinearProgressIndicator,
|
||||
val message: TextView
|
||||
) : ProgressMonitor {
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.adapters.TemplateWidgetsListAdapter
|
||||
import com.itsaky.androidide.databinding.FragmentTemplateDetailsBinding
|
||||
import com.itsaky.androidide.templates.Template
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel
|
||||
|
||||
/**
|
||||
* A fragment which shows a wizard-like interface for creating templates.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TemplateDetailsFragment :
|
||||
FragmentWithBinding<FragmentTemplateDetailsBinding>(
|
||||
R.layout.fragment_template_details, FragmentTemplateDetailsBinding::bind) {
|
||||
|
||||
private val viewModel by viewModels<MainViewModel>(
|
||||
ownerProducer = { requireActivity() })
|
||||
|
||||
private val template: Template
|
||||
get() = checkNotNull(
|
||||
viewModel.template.value) { "Invalid template data stored in view model" }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.template.observe(viewLifecycleOwner) {
|
||||
it ?: return@observe
|
||||
|
||||
binding.widgets.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.widgets.adapter = TemplateWidgetsListAdapter(template.widgets)
|
||||
|
||||
binding.title.setText(template.templateName)
|
||||
binding.previous.setOnClickListener {
|
||||
viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_LIST)
|
||||
}
|
||||
|
||||
binding.widgets.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
binding.widgets.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.flexbox.FlexDirection
|
||||
import com.google.android.flexbox.FlexboxLayoutManager
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.adapters.TemplateListAdapter
|
||||
import com.itsaky.androidide.databinding.FragmentTemplateListBinding
|
||||
import com.itsaky.androidide.templates.ITemplateProvider
|
||||
import com.itsaky.androidide.viewmodel.MainViewModel
|
||||
import kotlin.math.ceil
|
||||
|
||||
/**
|
||||
* A fragment to show the list of available templates.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TemplateListFragment : FragmentWithBinding<FragmentTemplateListBinding>(
|
||||
R.layout.fragment_template_list, FragmentTemplateListBinding::bind) {
|
||||
|
||||
private var adapter: TemplateListAdapter? = null
|
||||
private var layoutManager: FlexboxLayoutManager? = null
|
||||
|
||||
private val viewModel by viewModels<MainViewModel>(
|
||||
ownerProducer = { requireActivity() })
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val templates = ITemplateProvider.getInstance().getTemplates()
|
||||
layoutManager =
|
||||
FlexboxLayoutManager(requireContext(), FlexDirection.ROW).apply {
|
||||
justifyContent = JustifyContent.SPACE_EVENLY
|
||||
}
|
||||
|
||||
adapter = TemplateListAdapter(templates) { template, _ ->
|
||||
viewModel.template.value = template
|
||||
viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS)
|
||||
}
|
||||
|
||||
binding.list.layoutManager = layoutManager
|
||||
binding.list.adapter = adapter
|
||||
|
||||
// This makes sure that the items are evenly distributed in the list
|
||||
// and the last row is always aligned to the start
|
||||
binding.list.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
binding.list.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
|
||||
val adapter = this@TemplateListFragment.adapter ?: return
|
||||
val layoutManager = this@TemplateListFragment.layoutManager ?: return
|
||||
|
||||
val columns =
|
||||
layoutManager.flexLinesInternal.firstOrNull()?.itemCount ?: 0
|
||||
if (columns == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val itemCount = adapter.itemCount
|
||||
val rows = ceil(itemCount.toFloat() / columns.toFloat()).toInt()
|
||||
if (itemCount % columns == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val diff = rows * columns - itemCount
|
||||
if (diff <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
adapter.fillDiff(diff)
|
||||
}
|
||||
})
|
||||
|
||||
binding.exitButton.setOnClickListener {
|
||||
viewModel.setScreen(MainViewModel.SCREEN_MAIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.viewmodel
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.itsaky.androidide.templates.Template
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* [ViewModel] for main activity.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class MainViewModel : ViewModel() {
|
||||
|
||||
companion object {
|
||||
|
||||
// main screens
|
||||
const val SCREEN_MAIN = 0
|
||||
const val SCREEN_TEMPLATE_LIST = 1
|
||||
const val SCREEN_TEMPLATE_DETAILS = 2
|
||||
}
|
||||
|
||||
private val _currentScreen = MutableLiveData(-1)
|
||||
private val _previousScreen = AtomicInteger(-1)
|
||||
|
||||
internal val template = MutableLiveData<Template>(null)
|
||||
|
||||
val currentScreen: LiveData<Int> = _currentScreen
|
||||
val previousScreen: Int
|
||||
get() = _previousScreen.get()
|
||||
|
||||
fun setScreen(screen: Int) {
|
||||
_previousScreen.set(_currentScreen.value ?: SCREEN_MAIN)
|
||||
_currentScreen.value = screen
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,33 @@
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:id="@+id/main"
|
||||
android:name="com.itsaky.androidide.fragments.MainFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:name="com.itsaky.androidide.fragments.MainFragment" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/template_list"
|
||||
android:name="com.itsaky.androidide.fragments.TemplateListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/template_details"
|
||||
android:name="com.itsaky.androidide.fragments.TemplateDetailsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
||||
android:transitionName="templateName"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/new_project" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/widgets"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/previous"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/previous"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/previous"
|
||||
android:transitionName="negativeButton"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/finish"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/create_project"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/new_project"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
||||
android:transitionName="title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/exit_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
tools:itemCount="10"
|
||||
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
tools:listitem="@layout/layout_template_list_item"
|
||||
tools:spanCount="3" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/exit_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/exit"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/template_list"
|
||||
android:name="com.itsaky.androidide.fragments.TemplateListFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/template_details"
|
||||
android:name="com.itsaky.androidide.fragments.TemplateDetailsFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
+4
-3
@@ -13,12 +13,13 @@
|
||||
app:strokeWidth="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
@@ -36,7 +37,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="Empty Activity" />
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical" />
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -8,5 +9,9 @@
|
||||
android:id="@+id/template_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/wizard_template_item" />
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/layout_template_list_item"
|
||||
tools:spanCount="3" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -41,6 +41,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21")
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,11 @@ import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.FlashbarUtilsKt;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.utils.VMUtils;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class BaseApplication extends Application {
|
||||
public class BaseApplication extends Application {
|
||||
|
||||
public static final String NOTIFICATION_GRADLE_BUILD_SERVICE = "17571";
|
||||
public static final String TELEGRAM_GROUP_URL = "https://t.me/androidide_discussions";
|
||||
@@ -85,7 +86,10 @@ public abstract class BaseApplication extends Application {
|
||||
|
||||
mPrefsManager = new PreferenceManager(this);
|
||||
JavaCharacter.initMap();
|
||||
|
||||
if (!VMUtils.isJvm()) {
|
||||
ToolsManager.init(this, null);
|
||||
}
|
||||
|
||||
createNotificationChannels();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.viewbinding.ViewBinding
|
||||
|
||||
/**
|
||||
* Handles inflation and release of [ViewBinding][androidx.viewbinding.ViewBinding] for the fragment.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class FragmentWithBinding<T : ViewBinding>(@LayoutRes layout: Int,
|
||||
private val bind: (View) -> T
|
||||
) : BaseFragment(layout) {
|
||||
|
||||
@Suppress("PropertyName")
|
||||
protected var _binding: T? = null
|
||||
|
||||
protected val binding: T
|
||||
get() = checkNotNull(
|
||||
_binding) { "Cannot access ViewBinding. Fragment may have been destroyed." }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return super.onCreateView(inflater, container, savedInstanceState)!!
|
||||
.also { _binding = bind(it) }
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,10 @@ androidx-ktx = { module = "androidx.core:core-ktx", version = "1.10.0" }
|
||||
androidx-fragment_ktx = { module = "androidx.fragment:fragment-ktx", version = "1.5.7" }
|
||||
androidx-lib_desugaring = { module = "com.android.tools:desugar_jdk_libs", version = "1.1.6" }
|
||||
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
androidx-nav_frag = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
|
||||
androidx-nav_ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
|
||||
androidx-nav_dynamic_features = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "androidx-navigation" }
|
||||
androidx-transition = { module = "androidx.transition:transition-ktx", version = "1.4.1" }
|
||||
androidx-nav-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
|
||||
androidx-nav-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
|
||||
androidx-nav-dynamic_features = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "androidx-navigation" }
|
||||
|
||||
# Google
|
||||
google-material = { module = "com.google.android.material:material", version = "1.8.0" }
|
||||
@@ -73,6 +74,7 @@ google-auto-service = { module = "com.google.auto.service:auto-service", version
|
||||
google-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version = "17.0.0" }
|
||||
google-protobuf = { module = "com.google.protobuf:protobuf-java", version = "3.22.3" }
|
||||
google-java-format = { module ="com.google.googlejavaformat:google-java-format", version="1.16.0" }
|
||||
google-flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" }
|
||||
|
||||
# AAPT2
|
||||
aapt2-common = { module = "com.android.tools:common", version = "30.2.2" }
|
||||
|
||||
@@ -553,5 +553,12 @@
|
||||
<string name="permdesc_bind_logger_service">Allows this application to bind to AndroidIDE\'s logging service. This permission is required for AndroidIDE to read logs of this application.</string>
|
||||
<string name="wizard_module_name">Module name</string>
|
||||
<string name="wizard_module_type">Module type</string>
|
||||
|
||||
<string name="msg_invalid_module_name">Invalid Gradle module name</string>
|
||||
<string name="msg_invalid_class_name">Invalid class name</string>
|
||||
<string name="msg_value_empty">Value is empty</string>
|
||||
<string name="msg_file_not_exist">File does not exist</string>
|
||||
<string name="msg_path_must_be_file">Must be path to a file</string>
|
||||
<string name="msg_classname_with_keywords">Class name contains keywords</string>
|
||||
<string name="msg_path_must_be_dir">Must be path to a directory</string>
|
||||
<string name="msg_invalid_layout_name">Invalid layout file name</string>
|
||||
</resources>
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.templates
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.itsaky.androidide.utils.ServiceLoader
|
||||
|
||||
/**
|
||||
* [ITemplateWidgetViewProvider] creates [views][View] for various type of [widgets][Widget].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ITemplateWidgetViewProvider {
|
||||
|
||||
companion object {
|
||||
|
||||
private var service: ITemplateWidgetViewProvider? = null
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getInstance(reload: Boolean = false): ITemplateWidgetViewProvider {
|
||||
if (reload) {
|
||||
service = null
|
||||
}
|
||||
return service ?: ServiceLoader.load(
|
||||
ITemplateWidgetViewProvider::class.java)
|
||||
.findFirstOrThrow()
|
||||
.also { service = it }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates [View] objects for the given widget.
|
||||
*
|
||||
* @param context The context used to create the views.
|
||||
* @param widget The widget to create the view for.
|
||||
* @return The view.
|
||||
*/
|
||||
fun createView(context: Context, widget: Widget<*>): View
|
||||
}
|
||||
@@ -64,6 +64,7 @@ fun baseProject(block: ProjectTemplateConfigurator): ProjectTemplate {
|
||||
val projectName = stringParameter {
|
||||
name = R.string.project_app_name
|
||||
default = "My Application"
|
||||
startIcon = R.drawable.ic_android
|
||||
constraints = listOf(NONEMPTY)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,15 @@ internal fun ProjectTemplateData.moduleNameToDir(name: String): File {
|
||||
return File(this.projectDir, moduleNameToDirName(name).replace(':', '/').trim { it == '/' })
|
||||
}
|
||||
|
||||
fun isValidModuleName(name: String): Boolean {
|
||||
if (name.length < 2 || name.isBlank() || name[0] != ':') {
|
||||
return false
|
||||
}
|
||||
|
||||
val moduleName = name.substring(1)
|
||||
return moduleNameToDirName(moduleName) == moduleName
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Gradle module name to a directory name.
|
||||
*
|
||||
@@ -56,8 +65,8 @@ fun moduleNameToDirName(name: String): String {
|
||||
// the first character must be a letter
|
||||
(i == 0 && !c.isLetter())
|
||||
|
||||
// chars at other indices must be a letter or digit or
|
||||
|| !(c.isDigit() || c.isLetter() || c == '-')
|
||||
// chars at other indices must be a letter, digit, hyphen or an underscore
|
||||
|| !(c.isDigit() || c.isLetter() || c == '-' || c =='_')
|
||||
|
||||
// must not include consecutive '-'
|
||||
|| (prev == '-' && c == '-')
|
||||
|
||||
@@ -74,8 +74,9 @@ enum class ParameterConstraint {
|
||||
|
||||
typealias ValueSuggestion<T> = Parameter<T>.() -> T?
|
||||
|
||||
abstract class Parameter<T>(@StringRes val name: Int, @StringRes val description: Int?,
|
||||
val default: T, var constraints: List<ParameterConstraint>,
|
||||
abstract class Parameter<T>(@StringRes val name: Int,
|
||||
@StringRes val description: Int?, val default: T,
|
||||
var constraints: List<ParameterConstraint>,
|
||||
var suggest: ValueSuggestion<T>
|
||||
) {
|
||||
|
||||
@@ -104,39 +105,83 @@ abstract class ParameterBuilder<T>() {
|
||||
abstract fun build(): Parameter<T>
|
||||
}
|
||||
|
||||
class StringParameter(@StringRes name: Int, @StringRes description: Int?, default: String,
|
||||
constraints: List<ParameterConstraint>, suggest: ValueSuggestion<String>
|
||||
) : Parameter<String>(name, description, default, constraints, suggest)
|
||||
|
||||
class StringParameterBuilder : ParameterBuilder<String>() {
|
||||
|
||||
override fun build(): StringParameter {
|
||||
return StringParameter(name!!, description, default!!, constraints, suggest)
|
||||
}
|
||||
}
|
||||
|
||||
class BooleanParameter(@StringRes name: Int, @StringRes description: Int?, default: Boolean,
|
||||
constraints: List<ParameterConstraint>, suggest: ValueSuggestion<Boolean>
|
||||
class BooleanParameter(@StringRes name: Int, @StringRes description: Int?,
|
||||
default: Boolean, constraints: List<ParameterConstraint>,
|
||||
suggest: ValueSuggestion<Boolean>
|
||||
) : Parameter<Boolean>(name, description, default, constraints, suggest)
|
||||
|
||||
class BooleanParameterBuilder : ParameterBuilder<Boolean>() {
|
||||
|
||||
override fun build(): BooleanParameter {
|
||||
return BooleanParameter(name!!, description, default!!, constraints, suggest)
|
||||
return BooleanParameter(name!!, description, default!!, constraints,
|
||||
suggest)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for parameters which are presented using a text field.
|
||||
*
|
||||
* @property startIcon The drawable resource that will be used as the start icon.
|
||||
* @property endIcon The drawable resource that will be used as the end icon.
|
||||
* @property onStartIconClick Function which will be used when the start icon
|
||||
* is clicked. Click listener to the icon will be set on if this is non-null.
|
||||
* @property onEndIconClick Function which will be used when the end icon is
|
||||
* clicked. Click listener to the icon will be set on if this is non-null.
|
||||
*/
|
||||
abstract class TextFieldParameter<T>(@StringRes name: Int,
|
||||
@StringRes description: Int?, default: T,
|
||||
val startIcon: Int?, val endIcon: Int?,
|
||||
val onStartIconClick: (() -> Unit)?,
|
||||
val onEndIconClick: (() -> Unit)?,
|
||||
constraints: List<ParameterConstraint>,
|
||||
suggest: ValueSuggestion<T>
|
||||
) : Parameter<T>(name, description, default, constraints, suggest)
|
||||
|
||||
abstract class TextFieldParameterBuilder<T>(var startIcon: Int? = null,
|
||||
var endIcon: Int? = null,
|
||||
var onStartIconClick: (() -> Unit)? = null,
|
||||
var onEndIconClick: (() -> Unit)? = null
|
||||
) : ParameterBuilder<T>()
|
||||
|
||||
class StringParameter(@StringRes name: Int, @StringRes description: Int?,
|
||||
default: String, startIcon: Int?, endIcon: Int?,
|
||||
onStartIconClick: (() -> Unit)?,
|
||||
onEndIconClick: (() -> Unit)?,
|
||||
constraints: List<ParameterConstraint>,
|
||||
suggest: ValueSuggestion<String>
|
||||
) : TextFieldParameter<String>(name, description, default, startIcon, endIcon,
|
||||
onStartIconClick, onEndIconClick, constraints, suggest)
|
||||
|
||||
class StringParameterBuilder() : TextFieldParameterBuilder<String>() {
|
||||
|
||||
override fun build(): StringParameter {
|
||||
return StringParameter(name = name!!, description = description,
|
||||
default = default!!, startIcon = startIcon, endIcon = endIcon,
|
||||
onStartIconClick = onStartIconClick, onEndIconClick = onEndIconClick,
|
||||
constraints = constraints, suggest = suggest)
|
||||
}
|
||||
}
|
||||
|
||||
class EnumParameter<T : Enum<*>>(@StringRes name: Int, @StringRes description: Int?, default: T,
|
||||
class EnumParameter<T : Enum<*>>(@StringRes name: Int,
|
||||
@StringRes description: Int?, default: T,
|
||||
startIcon: Int?, endIcon: Int?,
|
||||
onStartIconClick: (() -> Unit)?,
|
||||
onEndIconClick: (() -> Unit)?,
|
||||
constraints: List<ParameterConstraint>,
|
||||
suggest: ValueSuggestion<T>, val displayName: ((T) -> String)? = null
|
||||
) : Parameter<T>(name, description, default, constraints, suggest)
|
||||
suggest: ValueSuggestion<T>,
|
||||
val displayName: ((T) -> String)? = null
|
||||
) : TextFieldParameter<T>(name, description, default, startIcon, endIcon,
|
||||
onStartIconClick, onEndIconClick, constraints, suggest)
|
||||
|
||||
class EnumParameterBuilder<T : Enum<*>> : ParameterBuilder<T>() {
|
||||
class EnumParameterBuilder<T : Enum<*>> : TextFieldParameterBuilder<T>() {
|
||||
|
||||
var displayName: ((T) -> String)? = null
|
||||
|
||||
override fun build(): EnumParameter<T> {
|
||||
return EnumParameter(name = name!!, description = description, default = default!!,
|
||||
return EnumParameter(name = name!!, description = description,
|
||||
default = default!!, startIcon = startIcon, endIcon = endIcon,
|
||||
onStartIconClick = onStartIconClick, onEndIconClick = onEndIconClick,
|
||||
constraints = constraints, suggest = suggest, displayName = displayName)
|
||||
}
|
||||
}
|
||||
@@ -153,5 +198,5 @@ fun stringParameter(block: StringParameterBuilder.() -> Unit
|
||||
fun booleanParameter(block: BooleanParameterBuilder.() -> Unit
|
||||
): BooleanParameter = BooleanParameterBuilder().apply(block).build()
|
||||
|
||||
fun <T : Enum<*>> enumParameter(block: EnumParameterBuilder<T>.() -> Unit): EnumParameter<T> =
|
||||
EnumParameterBuilder<T>().apply(block).build()
|
||||
fun <T : Enum<*>> enumParameter(block: EnumParameterBuilder<T>.() -> Unit
|
||||
): EnumParameter<T> = EnumParameterBuilder<T>().apply(block).build()
|
||||
@@ -195,6 +195,15 @@ open class Template(@StringRes open val templateName: Int,
|
||||
|
||||
open val parameters: Collection<Parameter<*>>
|
||||
get() = widgets.filterIsInstance<ParameterWidget<*>>().map { it.parameter }
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
private val EMPTY_RECIPE: TemplateRecipe = {}
|
||||
|
||||
@JvmStatic
|
||||
val EMPTY = Template(-1, -1, -1, emptyList(), EMPTY_RECIPE)
|
||||
}
|
||||
}
|
||||
|
||||
open class ProjectTemplate(val moduleTemplates: List<Template>,
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation(projects.common)
|
||||
implementation(projects.preferences)
|
||||
implementation(projects.subprojects.projects)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.ktx)
|
||||
implementation(libs.google.auto.service.annotations)
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.templates.impl
|
||||
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.templates.ParameterConstraint
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.CLASS
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.CLASS_NAME
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.DIRECTORY
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.EXISTS
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.FILE
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.LAYOUT
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.MODULE_NAME
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.NONEMPTY
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.PACKAGE
|
||||
import com.itsaky.androidide.templates.base.util.isValidModuleName
|
||||
import com.itsaky.androidide.templates.impl.R.string
|
||||
import com.itsaky.androidide.utils.AndroidUtils
|
||||
import jdkx.lang.model.SourceVersion
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Verifies constraint for text field widget.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object ConstraintVerifier {
|
||||
|
||||
/**
|
||||
* Checks whether the given input is valid against the given constraints.
|
||||
*
|
||||
* @see verify
|
||||
*/
|
||||
fun isValid(input: String, constraints: List<ParameterConstraint>): Boolean =
|
||||
verify(input, constraints) == null
|
||||
|
||||
/**
|
||||
* Verify the input against the given constraints.
|
||||
*
|
||||
* @param input The input to verify.
|
||||
* @param constraints The constraints to verify against.
|
||||
* @return The error message if the validation fails, `null` otherwise.
|
||||
*/
|
||||
fun verify(input: String, constraints: List<ParameterConstraint>): String? {
|
||||
var err: String? = null
|
||||
for (constraint in constraints) {
|
||||
err = when (constraint) {
|
||||
NONEMPTY -> checkNotEmpty(input)
|
||||
PACKAGE -> validatePackageName(input)
|
||||
CLASS -> validateClassName(input)
|
||||
CLASS_NAME -> validateSimpleName(input)
|
||||
MODULE_NAME -> validateModuleName(input)
|
||||
LAYOUT -> validateLayoutName(input)
|
||||
EXISTS -> checKFileExists(input)
|
||||
FILE -> checkIsFile(input)
|
||||
DIRECTORY -> checkIsDirectory(input)
|
||||
else -> throw IllegalArgumentException(
|
||||
"Unsupported parameter constraint '$constraint'")
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun validatePackageName(input: String): String? {
|
||||
AndroidUtils.validatePackageName(input)?.let { return it }
|
||||
|
||||
if (SourceVersion.isName(input)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(R.string.msg_package_is_not_valid)
|
||||
}
|
||||
|
||||
private fun validateLayoutName(input: String): String? {
|
||||
if (input.isBlank()) {
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(R.string.msg_value_empty)
|
||||
}
|
||||
|
||||
// Allow only lowecase letters with digits and underscores
|
||||
if (input.matches(Regex("[a-z][a-z0-9_]+"))) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_invalid_layout_name)
|
||||
}
|
||||
|
||||
private fun checkIsDirectory(input: String): String? {
|
||||
checKFileExists(input)?.let { return it }
|
||||
|
||||
val file = File(input)
|
||||
if (file.isDirectory) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_path_must_be_dir)
|
||||
}
|
||||
|
||||
private fun checkIsFile(input: String): String? {
|
||||
checKFileExists(input)?.let { return it }
|
||||
|
||||
val file = File(input)
|
||||
if (file.isFile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_path_must_be_file)
|
||||
}
|
||||
|
||||
private fun checKFileExists(input: String): String? {
|
||||
val file = File(input)
|
||||
if (file.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_file_not_exist)
|
||||
}
|
||||
|
||||
private fun checkNotEmpty(input: String): String? {
|
||||
if (input.isNotBlank()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance().getString(string.msg_value_empty)
|
||||
}
|
||||
|
||||
private fun validateModuleName(input: String): String? {
|
||||
if (isValidModuleName(input)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_invalid_module_name)
|
||||
}
|
||||
|
||||
private fun validateClassName(name: String): String? {
|
||||
if (!name.contains('.')) {
|
||||
return validateSimpleName(name)
|
||||
}
|
||||
|
||||
val pck = name.substring(0, name.lastIndexOf('.'))
|
||||
var err: String? = null
|
||||
if (pck.contains('.')) {
|
||||
err = validatePackageName(pck)
|
||||
} else {
|
||||
if (!SourceVersion.isIdentifier(pck) || SourceVersion.isKeyword(pck)) {
|
||||
err = BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_package_is_not_valid)
|
||||
}
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateSimpleName(name.substring(name.lastIndexOf('.') + 1))
|
||||
}
|
||||
|
||||
private fun validateSimpleName(name: String): String? {
|
||||
if (SourceVersion.isKeyword(name) || !SourceVersion.isIdentifier(name)) {
|
||||
return BaseApplication.getBaseInstance()
|
||||
.getString(string.msg_classname_with_keywords)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.templates.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.auto.service.AutoService
|
||||
import com.itsaky.androidide.templates.BooleanParameter
|
||||
import com.itsaky.androidide.templates.CheckBoxWidget
|
||||
import com.itsaky.androidide.templates.EnumParameter
|
||||
import com.itsaky.androidide.templates.ITemplateWidgetViewProvider
|
||||
import com.itsaky.androidide.templates.SpinnerWidget
|
||||
import com.itsaky.androidide.templates.StringParameter
|
||||
import com.itsaky.androidide.templates.TextFieldParameter
|
||||
import com.itsaky.androidide.templates.TextFieldWidget
|
||||
import com.itsaky.androidide.templates.Widget
|
||||
import com.itsaky.androidide.templates.impl.databinding.LayoutCheckboxBinding
|
||||
import com.itsaky.androidide.templates.impl.databinding.LayoutSpinnerBinding
|
||||
import com.itsaky.androidide.templates.impl.databinding.LayoutTextfieldBinding
|
||||
import com.itsaky.androidide.utils.ServiceLoader
|
||||
import com.itsaky.androidide.utils.SingleTextWatcher
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of [ITemplateWidgetViewProvider].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@AutoService(ITemplateWidgetViewProvider::class)
|
||||
class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
private var service: ITemplateWidgetViewProvider? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(reload: Boolean = false): ITemplateWidgetViewProvider {
|
||||
if (reload) {
|
||||
service = null
|
||||
}
|
||||
return ServiceLoader.load(ITemplateWidgetViewProvider::class.java)
|
||||
.findFirstOrThrow()
|
||||
.also { service = it }
|
||||
}
|
||||
}
|
||||
|
||||
override fun createView(context: Context, widget: Widget<*>): View {
|
||||
return when (widget) {
|
||||
is TextFieldWidget -> createTextField(context, widget)
|
||||
is CheckBoxWidget -> createCheckBox(context, widget)
|
||||
is SpinnerWidget -> createSpinner(context, widget)
|
||||
else -> throw IllegalArgumentException("Unknown widget type : $widget")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCheckBox(context: Context, widget: CheckBoxWidget): View {
|
||||
return LayoutCheckboxBinding.inflate(LayoutInflater.from(context)).apply {
|
||||
val param = widget.parameter as BooleanParameter
|
||||
root.setText(param.name)
|
||||
root.isChecked = param.default
|
||||
|
||||
root.addOnCheckedStateChangedListener { checkbox, _ ->
|
||||
param.value = checkbox.isChecked
|
||||
}
|
||||
}.root
|
||||
}
|
||||
|
||||
private fun createTextField(context: Context, widget: TextFieldWidget): View {
|
||||
return LayoutTextfieldBinding.inflate(LayoutInflater.from(context)).apply {
|
||||
val param = widget.parameter as StringParameter
|
||||
param.configureTextField(context, root) { value ->
|
||||
param.value = value
|
||||
val err =
|
||||
ConstraintVerifier.verify(value, constraints = param.constraints)
|
||||
|
||||
root.isErrorEnabled = err != null
|
||||
if (err != null) {
|
||||
root.error = err
|
||||
}
|
||||
}
|
||||
input.setText(param.default)
|
||||
}.root
|
||||
}
|
||||
|
||||
private fun createSpinner(context: Context, widget: SpinnerWidget<*>): View {
|
||||
return LayoutSpinnerBinding.inflate(LayoutInflater.from(context)).apply {
|
||||
val param = widget.parameter as EnumParameter<Enum<*>>
|
||||
|
||||
val items = mutableMapOf<String, Enum<*>>()
|
||||
param.default.javaClass.enumConstants?.forEach {
|
||||
println("Enum constant: $it")
|
||||
val displayName = param.displayName?.invoke(it) ?: it.name
|
||||
items[displayName] = it
|
||||
}
|
||||
|
||||
check(
|
||||
items.isNotEmpty()) { "Cannot retrive values for enum parameter $param with default value ${param.default}" }
|
||||
|
||||
val array = items.keys.toTypedArray()
|
||||
|
||||
println("EnumParam entries : ${array.joinToString(", ")}")
|
||||
|
||||
input.setAdapter(ArrayAdapter(context,
|
||||
androidx.appcompat.R.layout.support_simple_spinner_dropdown_item,
|
||||
array))
|
||||
|
||||
val defaultName =
|
||||
param.displayName?.invoke(param.default) ?: param.default.name
|
||||
|
||||
root.isEnabled = items.size > 1
|
||||
input.listSelection = array.indexOf(defaultName)
|
||||
input.setText(defaultName, false)
|
||||
|
||||
param.configureTextField(context, root) {
|
||||
param.value = items[it] ?: param.default
|
||||
}
|
||||
}.root
|
||||
}
|
||||
|
||||
private fun TextFieldParameter<*>.configureTextField(context: Context,
|
||||
root: TextInputLayout,
|
||||
onTextChanged: (String) -> Unit = {}
|
||||
) {
|
||||
root.setHint(name)
|
||||
|
||||
startIcon?.let {
|
||||
root.startIconDrawable = ContextCompat.getDrawable(context, it)
|
||||
onStartIconClick?.let { onClick ->
|
||||
root.setStartIconOnClickListener { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
endIcon?.let {
|
||||
root.endIconDrawable = ContextCompat.getDrawable(context, it)
|
||||
onEndIconClick?.let { onClick ->
|
||||
root.setEndIconOnClickListener { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
root.editText!!.addTextChangedListener(object : SingleTextWatcher() {
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int,
|
||||
count: Int
|
||||
) {
|
||||
onTextChanged(s?.toString() ?: "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Use Kotlin Script (.kts)" />
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:hint="Choose value">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:hint="Project name">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
@@ -18,22 +18,123 @@
|
||||
package com.itsaky.androidide.templates
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.CLASS
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.CLASS_NAME
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.DIRECTORY
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.EXISTS
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.FILE
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.LAYOUT
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.MODULE_NAME
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.NONEMPTY
|
||||
import com.itsaky.androidide.templates.ParameterConstraint.PACKAGE
|
||||
import com.itsaky.androidide.templates.base.util.isValidModuleName
|
||||
import com.itsaky.androidide.templates.base.util.moduleNameToDirName
|
||||
import com.itsaky.androidide.templates.impl.ConstraintVerifier
|
||||
import com.itsaky.androidide.utils.FileProvider
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = BaseApplication::class)
|
||||
class UtilTest {
|
||||
|
||||
@Test
|
||||
fun `test module name conversion`() {
|
||||
val tests = mapOf("2app" to "app", "app2" to "app2", "2app2" to "app2", "2 app2" to "app2",
|
||||
"app name" to "app-name", "app name" to "app-name", "app-name" to "app-name",
|
||||
"app--name" to "app-name")
|
||||
val tests = mapOf("2app" to "app", "app2" to "app2", "2app2" to "app2",
|
||||
"2 app2" to "app2", "app name" to "app-name", "app name" to "app-name",
|
||||
"app-name" to "app-name", "app--name" to "app-name",
|
||||
"my_module" to "my_module")
|
||||
|
||||
tests.forEach { (input, expected) ->
|
||||
assertThat(moduleNameToDirName(input)).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test module name validation`() {
|
||||
val tests = mapOf("2app" to false, "app2" to true, "2app2" to false,
|
||||
"2 app2" to false, "app name" to false, "app name" to false,
|
||||
"app-name" to true, "app--name" to false)
|
||||
|
||||
tests.forEach { (name, result) ->
|
||||
println("Check $name")
|
||||
assertThat(isValidModuleName(":$name")).isEqualTo(result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test constraint verifier`() {
|
||||
ConstraintVerifier.apply {
|
||||
|
||||
assertThat(isValid("", listOf(NONEMPTY))).isFalse()
|
||||
assertThat(isValid("something", listOf(NONEMPTY))).isTrue()
|
||||
|
||||
assertThat(isValid("activity_main", listOf(LAYOUT))).isTrue()
|
||||
assertThat(isValid("2activity_main", listOf(LAYOUT))).isFalse()
|
||||
|
||||
assertThat(isValid("2.invalid.package", listOf(PACKAGE))).isFalse()
|
||||
assertThat(isValid("invalid", listOf(PACKAGE))).isFalse()
|
||||
assertThat(isValid("2invalid.package", listOf(PACKAGE))).isFalse()
|
||||
assertThat(isValid("invalid.package", listOf(PACKAGE))).isFalse()
|
||||
assertThat(isValid("inval0d.PacKage", listOf(PACKAGE))).isFalse()
|
||||
assertThat(isValid("com.itsaky.androidide", listOf(PACKAGE))).isTrue()
|
||||
|
||||
assertThat(isValid("Class", listOf(CLASS))).isTrue()
|
||||
assertThat(isValid("pck.name.Class", listOf(CLASS))).isTrue()
|
||||
assertThat(isValid("pck.name.Class_Name", listOf(CLASS))).isTrue()
|
||||
assertThat(isValid("pck.name.Class____Name", listOf(CLASS))).isTrue()
|
||||
assertThat(isValid("p443ackage.Class_Name", listOf(CLASS))).isTrue()
|
||||
assertThat(isValid("package.2Class", listOf(CLASS))).isFalse()
|
||||
assertThat(isValid("package.Class", listOf(CLASS))).isFalse()
|
||||
assertThat(isValid("package.name.Class", listOf(CLASS))).isFalse()
|
||||
|
||||
assertThat(isValid("ClassName", listOf(CLASS_NAME))).isTrue()
|
||||
assertThat(isValid("classname", listOf(CLASS_NAME))).isTrue()
|
||||
assertThat(isValid("class_name", listOf(CLASS_NAME))).isTrue()
|
||||
assertThat(isValid("class__name", listOf(CLASS_NAME))).isTrue()
|
||||
assertThat(isValid("2class__name", listOf(CLASS_NAME))).isFalse()
|
||||
assertThat(isValid("2class.name", listOf(CLASS_NAME))).isFalse()
|
||||
|
||||
assertThat(isValid(":app", listOf(MODULE_NAME))).isTrue()
|
||||
assertThat(isValid(":app-name", listOf(MODULE_NAME))).isTrue()
|
||||
assertThat(isValid(":app_name", listOf(MODULE_NAME))).isTrue()
|
||||
assertThat(isValid(":my_module_num_2", listOf(MODULE_NAME))).isTrue()
|
||||
assertThat(isValid(":2app", listOf(MODULE_NAME))).isFalse()
|
||||
assertThat(isValid("2app", listOf(MODULE_NAME))).isFalse()
|
||||
assertThat(isValid(":_app", listOf(MODULE_NAME))).isFalse()
|
||||
|
||||
assertThat(isValid("activity_main", listOf(LAYOUT))).isTrue()
|
||||
assertThat(isValid("fragment__main", listOf(LAYOUT))).isTrue()
|
||||
assertThat(isValid("layout_main", listOf(LAYOUT))).isTrue()
|
||||
assertThat(isValid("Activity_Main", listOf(LAYOUT))).isFalse()
|
||||
assertThat(isValid("ActivityMain", listOf(LAYOUT))).isFalse()
|
||||
assertThat(isValid("2activity_main", listOf(LAYOUT))).isFalse()
|
||||
assertThat(isValid("_activity_main", listOf(LAYOUT))).isFalse()
|
||||
|
||||
val build = FileProvider.currentDir().resolve("build").toFile()
|
||||
val file = File(build, "constraint_test_file.txt").also { it.writeText("Test file") }
|
||||
val nonExisting = File(build, "non_existing_constraint_test_file.txt")
|
||||
|
||||
assertThat(isValid(build.absolutePath, listOf(EXISTS))).isTrue()
|
||||
assertThat(isValid(build.absolutePath, listOf(DIRECTORY))).isTrue()
|
||||
assertThat(isValid(build.absolutePath, listOf(EXISTS, DIRECTORY))).isTrue()
|
||||
assertThat(isValid(build.absolutePath, listOf(FILE))).isFalse()
|
||||
|
||||
assertThat(isValid(file.absolutePath, listOf(EXISTS))).isTrue()
|
||||
assertThat(isValid(file.absolutePath, listOf(FILE))).isTrue()
|
||||
assertThat(isValid(file.absolutePath, listOf(EXISTS, FILE))).isTrue()
|
||||
assertThat(isValid(file.absolutePath, listOf(DIRECTORY))).isFalse()
|
||||
|
||||
assertThat(isValid(nonExisting.absolutePath, listOf(EXISTS))).isFalse()
|
||||
assertThat(isValid(nonExisting.absolutePath, listOf(FILE))).isFalse()
|
||||
assertThat(isValid(nonExisting.absolutePath, listOf(EXISTS, FILE))).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.ktx)
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.nav.frag)
|
||||
implementation(libs.androidx.nav.fragment)
|
||||
implementation(libs.androidx.nav.ui)
|
||||
implementation(libs.androidx.nav.dynamic.features)
|
||||
implementation(libs.common.editor)
|
||||
|
||||
Reference in New Issue
Block a user