From 08020cb02671e5a6ee5a8ae921b6c361a5ee342d Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 18 May 2023 22:31:37 +0530 Subject: [PATCH] feat(templates): add tabbed activity template --- .../tabbed/res/layout/activity_main.xml | 41 +++++ .../tabbed/res/layout/fragment_main.xml | 23 +++ .../templates/tabbed/res/values/dimens.xml | 8 + .../impl/tabbedActivity/srcActivity.kt | 106 +++++++++++ .../impl/tabbedActivity/srcPageViewModel.kt | 73 ++++++++ .../impl/tabbedActivity/srcPagerAdapter.kt | 110 +++++++++++ .../tabbedActivity/srcPlaceholderFragment.kt | 171 ++++++++++++++++++ .../tabbedActivity/tabbedActivityTemplate.kt | 86 +++++++++ .../templates/TemplateBuilderTest.kt | 8 + 9 files changed, 626 insertions(+) create mode 100644 templates-impl/src/main/assets/templates/tabbed/res/layout/activity_main.xml create mode 100644 templates-impl/src/main/assets/templates/tabbed/res/layout/fragment_main.xml create mode 100644 templates-impl/src/main/assets/templates/tabbed/res/values/dimens.xml create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcActivity.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPageViewModel.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPagerAdapter.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPlaceholderFragment.kt create mode 100644 templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/tabbedActivityTemplate.kt diff --git a/templates-impl/src/main/assets/templates/tabbed/res/layout/activity_main.xml b/templates-impl/src/main/assets/templates/tabbed/res/layout/activity_main.xml new file mode 100644 index 000000000..ce51618d7 --- /dev/null +++ b/templates-impl/src/main/assets/templates/tabbed/res/layout/activity_main.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + diff --git a/templates-impl/src/main/assets/templates/tabbed/res/layout/fragment_main.xml b/templates-impl/src/main/assets/templates/tabbed/res/layout/fragment_main.xml new file mode 100644 index 000000000..0a90c04ca --- /dev/null +++ b/templates-impl/src/main/assets/templates/tabbed/res/layout/fragment_main.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/templates-impl/src/main/assets/templates/tabbed/res/values/dimens.xml b/templates-impl/src/main/assets/templates/tabbed/res/values/dimens.xml new file mode 100644 index 000000000..0a87b6e73 --- /dev/null +++ b/templates-impl/src/main/assets/templates/tabbed/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + 16dp + 16dp + 16dp + 16dp + 8dp + \ No newline at end of file diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcActivity.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcActivity.kt new file mode 100644 index 000000000..7b252a9e2 --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcActivity.kt @@ -0,0 +1,106 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.templates.impl.tabbedActivity + +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder + +internal fun AndroidModuleTemplateBuilder.tabbedActivitySrcKt() = """ +package ${data.packageName} + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.viewpager.widget.ViewPager +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayout +import ${data.packageName}.ui.main.SectionsPagerAdapter +import ${data.packageName}.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + supportActionBar = binding.toolbar + + val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager) + val viewPager: ViewPager = binding.viewPager + val tabs: TabLayout = binding.tabs + + viewPager.adapter = sectionsPagerAdapter + tabs.setupWithViewPager(viewPager) + + binding.fab.setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + } + } +} +""".trim() + +internal fun AndroidModuleTemplateBuilder.tabbedActivitySrcJava() = """ +package ${data.packageName}; + +import android.os.Bundle; + +import com.google.android.material.snackbar.Snackbar; + +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; + +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import ${data.packageName}.ui.main.SectionsPagerAdapter; +import ${data.packageName}.databinding.ActivityMainBinding; + +public class MainActivity extends AppCompatActivity { + + private ActivityMainBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + setSupportActionBar(binding.toolbar); + + SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); + binding.viewPager.setAdapter(sectionsPagerAdapter); + binding.tabs.setupWithViewPager(binding.viewPager); + + binding.fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + } +} +""".trim() \ No newline at end of file diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPageViewModel.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPageViewModel.kt new file mode 100644 index 000000000..d0fa9e28c --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPageViewModel.kt @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.templates.impl.tabbedActivity + +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder + +internal fun AndroidModuleTemplateBuilder.tabbedPageViewModelSrcKt() + = """ +package ${data.packageName}.ui.main + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class PageViewModel : ViewModel() { + + private val _index = MutableLiveData() + val text: LiveData = Transformations.map(_index) { + "Hello world from section: ${'$'}it" + } + + fun setIndex(index: Int) { + _index.value = index + } +} +""".trim() + +internal fun AndroidModuleTemplateBuilder.tabbedPageViewModelSrcJava() + = """ +package ${data.packageName}.ui.main; + +import androidx.arch.core.util.Function; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; + +public class PageViewModel extends ViewModel { + + private MutableLiveData mIndex = new MutableLiveData<>(); + private LiveData mText = Transformations.map(mIndex, new Function() { + @Override + public String apply(Integer input) { + return "Hello world from section: " + input; + } + }); + + public void setIndex(int index) { + mIndex.setValue(index); + } + + public LiveData getText() { + return mText; + } +} +""".trim() \ No newline at end of file diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPagerAdapter.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPagerAdapter.kt new file mode 100644 index 000000000..a3af7e9fc --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPagerAdapter.kt @@ -0,0 +1,110 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.templates.impl.tabbedActivity + +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder + +internal fun AndroidModuleTemplateBuilder.tabbedPagerAdapterSrcKt() + = """ +package ${data.packageName}.ui.main + +import android.content.Context +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import ${data.packageName}.R + +private val TAB_TITLES = arrayOf( + R.string.tab_text_1, + R.string.tab_text_2, + R.string.tab_text_3 +) + +/** + * A [FragmentPagerAdapter] that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ +class SectionsPagerAdapter(private val context: Context, fm: FragmentManager) : + FragmentPagerAdapter(fm) { + + override fun getItem(position: Int): Fragment { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + return PlaceholderFragment.newInstance(position + 1) + } + + override fun getPageTitle(position: Int): CharSequence? { + return context.resources.getString(TAB_TITLES[position]) + } + + override fun getCount(): Int { + // Show 3 total pages. + return 3 + } +} +""".trim() + +internal fun AndroidModuleTemplateBuilder.tabbedPagerAdapterSrcJava() + = """ +package ${data.packageName}.ui.main; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +import ${data.packageName}.R; + +/** + * A [FragmentPagerAdapter] that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ +public class SectionsPagerAdapter extends FragmentPagerAdapter { + + @StringRes + private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2, R.string.tab_text_3}; + private final Context mContext; + + public SectionsPagerAdapter(Context context, FragmentManager fm) { + super(fm); + mContext = context; + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + return PlaceholderFragment.newInstance(position + 1); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mContext.getResources().getString(TAB_TITLES[position]); + } + + @Override + public int getCount() { + // Show 3 total pages. + return 3; + } +} +""".trim() \ No newline at end of file diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPlaceholderFragment.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPlaceholderFragment.kt new file mode 100644 index 000000000..540420684 --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/srcPlaceholderFragment.kt @@ -0,0 +1,171 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.templates.impl.tabbedActivity + +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder + +internal fun AndroidModuleTemplateBuilder.tabbedPlaceholderFragmentSrcKt() + = """ +package ${data.packageName}.ui.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import ${data.packageName}.R +import ${data.packageName}.databinding.FragmentMainBinding + +/** + * A placeholder fragment containing a simple view. + */ +class PlaceholderFragment : Fragment() { + + private lateinit var pageViewModel: PageViewModel + private var _binding: FragmentMainBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + pageViewModel = ViewModelProvider(this).get(PageViewModel::class.java).apply { + setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + _binding = FragmentMainBinding.inflate(inflater, container, false) + val root = binding.root + + val textView: TextView = binding.sectionLabel + pageViewModel.text.observe(viewLifecycleOwner, Observer { + textView.text = it + }) + return root + } + + companion object { + /** + * The fragment argument representing the section number for this + * fragment. + */ + private const val ARG_SECTION_NUMBER = "section_number" + + /** + * Returns a new instance of this fragment for the given section + * number. + */ + @JvmStatic + fun newInstance(sectionNumber: Int): PlaceholderFragment { + return PlaceholderFragment().apply { + arguments = Bundle().apply { + putInt(ARG_SECTION_NUMBER, sectionNumber) + } + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} +""".trim() + +internal fun AndroidModuleTemplateBuilder.tabbedPlaceholderFragmentSrcJava() + = """ +package ${data.packageName}.ui.main; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import ${data.packageName}.R; +import ${data.packageName}.databinding.FragmentMainBinding; + +/** + * A placeholder fragment containing a simple view. + */ +public class PlaceholderFragment extends Fragment { + + private static final String ARG_SECTION_NUMBER = "section_number"; + + private PageViewModel pageViewModel; + private FragmentMainBinding binding; + + public static PlaceholderFragment newInstance(int index) { + PlaceholderFragment fragment = new PlaceholderFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(ARG_SECTION_NUMBER, index); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pageViewModel = new ViewModelProvider(this).get(PageViewModel.class); + int index = 1; + if (getArguments() != null) { + index = getArguments().getInt(ARG_SECTION_NUMBER); + } + pageViewModel.setIndex(index); + } + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + binding = FragmentMainBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + + final TextView textView = binding.sectionLabel; + pageViewModel.getText().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(@Nullable String s) { + textView.setText(s); + } + }); + return root; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} +""".trim() \ No newline at end of file diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/tabbedActivityTemplate.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/tabbedActivityTemplate.kt new file mode 100644 index 000000000..e043bd31d --- /dev/null +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/tabbedActivity/tabbedActivityTemplate.kt @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.templates.impl.tabbedActivity + +import com.itsaky.androidide.templates.Language +import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder +import com.itsaky.androidide.templates.base.baseProject +import com.itsaky.androidide.templates.base.models.Dependency +import com.itsaky.androidide.templates.base.modules.android.defaultAppModule +import com.itsaky.androidide.templates.impl.base.emptyThemesAndColors +import com.itsaky.androidide.templates.impl.base.writeMainActivity +import com.itsaky.androidide.templates.impl.templateAsset + +fun tabbedActivityProject() = baseProject { + defaultAppModule { + recipe = { + sources { + writeMainActivity(this, ktSrc = ::tabbedActivitySrcKt, + javaSrc = ::tabbedActivitySrcJava) + } + + res { + copyAssetsRecursively(templateAsset("tabbed", "res"), mainResDir()) + + putStringRes("tab_text_1", "Tab 1") + putStringRes("tab_text_2", "Tab 2") + putStringRes("tab_text_3", "Tab 3") + + emptyThemesAndColors() + } + + if (data.language == Language.Kotlin) { + tabbedActivityProjectKt() + } else { + tabbedActivityProjectJava() + } + } + } +} + +fun AndroidModuleTemplateBuilder.tabbedActivityProjectKt() { + executor.apply { + addDependency(Dependency.AndroidX.LifeCycle_LiveData_Ktx) + addDependency(Dependency.AndroidX.LifeCycle_ViewModel_Ktx) + + sources { + writeKtSrc("${data.packageName}.ui.main", "SectionsPagerAdapter", + source = ::tabbedPagerAdapterSrcKt) + writeKtSrc("${data.packageName}.ui.main", "PageViewModel", + source = ::tabbedPageViewModelSrcKt) + writeKtSrc("${data.packageName}.ui.main", "PlaceholderFragment", + source = ::tabbedPlaceholderFragmentSrcKt) + } + } +} + +fun AndroidModuleTemplateBuilder.tabbedActivityProjectJava() { + executor.apply { + addDependency(Dependency.AndroidX.LifeCycle_LiveData) + addDependency(Dependency.AndroidX.LifeCycle_ViewModel) + + sources { + writeJavaSrc("${data.packageName}.ui.main", "SectionsPagerAdapter", + source = ::tabbedPagerAdapterSrcJava) + writeJavaSrc("${data.packageName}.ui.main", "PageViewModel", + source = ::tabbedPageViewModelSrcJava) + writeJavaSrc("${data.packageName}.ui.main", "PlaceholderFragment", + source = ::tabbedPlaceholderFragmentSrcJava) + } + } +} diff --git a/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt b/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt index 9f0a3569e..0d08f502f 100644 --- a/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt +++ b/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt @@ -27,6 +27,7 @@ import com.itsaky.androidide.templates.impl.basicActivity.basicActivityProject import com.itsaky.androidide.templates.impl.bottomNavActivity.bottomNavActivityProject import com.itsaky.androidide.templates.impl.emptyActivity.emptyActivityProject import com.itsaky.androidide.templates.impl.navDrawerActivity.navDrawerActivityProject +import com.itsaky.androidide.templates.impl.tabbedActivity.tabbedActivityProject import com.itsaky.androidide.xml.permissions.Permission.INTERNET import com.squareup.javapoet.ArrayTypeName import com.squareup.javapoet.ParameterSpec @@ -156,4 +157,11 @@ class TemplateBuilderTest { bottomNavActivityProject() } } + + @Test + fun `test tabbed activity project`() { + testTemplate("TabbedActivity") { + tabbedActivityProject() + } + } } \ No newline at end of file