feat(templates): add tabbed activity template

This commit is contained in:
Akash Yadav
2023-05-18 22:31:37 +05:30
parent 144b5694db
commit 08020cb026
9 changed files with 626 additions and 0 deletions
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.PlaceholderFragment">
<TextView
android:id="@+id/section_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/constraintLayout"
tools:layout_constraintLeft_creator="1"
tools:layout_constraintTop_creator="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="appbar_padding">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
</resources>
@@ -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 <https://www.gnu.org/licenses/>.
*/
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()
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>()
val text: LiveData<String> = 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<Integer> mIndex = new MutableLiveData<>();
private LiveData<String> mText = Transformations.map(mIndex, new Function<Integer, String>() {
@Override
public String apply(Integer input) {
return "Hello world from section: " + input;
}
});
public void setIndex(int index) {
mIndex.setValue(index);
}
public LiveData<String> getText() {
return mText;
}
}
""".trim()
@@ -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 <https://www.gnu.org/licenses/>.
*/
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()
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>() {
@Override
public void onChanged(@Nullable String s) {
textView.setText(s);
}
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
""".trim()
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}
}
@@ -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()
}
}
}