[Huawei Developers] Onurcan.Keskin:
View attachment 5178065

Building Activities for this Project

Before passing to the specialized flavor dimension let’s build:

Main Activity (Part-II)

Record Activity (Part-III)

Single Player (Fullscreen) Activity (Part-IV)

Profile Activity & Bonus Content (Part-V)

* Main Activity

Let’s create our menu items:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:enabled="true"
android:icon="@drawable/ic_home"
android:menuCategory="system"
android:title="@string/nav_home"
android:tooltipText="@string/nav_home"
app:showAsAction="always" />
<item
android:enabled="true"
android:icon="@drawable/ic_plus"
android:menuCategory="system"
android:title="@string/nav_record"
app:showAsAction="always" />
<item
android:enabled="true"
android:icon="@drawable/ic_profile"
android:menuCategory="system"
android:title="@string/nav_profile"
android:tooltipText="@string/nav_profile"
app:showAsAction="always" />
</menu>

Now our layouts, note that we will fill our Recycler View with different flavor binds. Exo Player for gms build and HMS Video Kit for our hms builds:

<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"
android:background="@color/black"
tools:context=".ui.activities.MainActivity">

<include
android:id="@+id/lottie_inc"
layout="@layout/lottie_loading"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/video_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="3dp"
app:layout_constraintBottom_toTopOf="@+id/view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_video" />

<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/white"
app:layout_constraintBottom_toTopOf="@+id/animatedBottomBar" />

<nl.joery.animatedbottombar.AnimatedBottomBar
android:id="@+id/animatedBottomBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/colour_bg"
app:abb_indicatorAppearance="round"
app:abb_indicatorColor="@color/white"
app:abb_indicatorHeight="4dp"
app:abb_indicatorLocation="bottom"
app:abb_indicatorMargin="16dp"
app:abb_rippleColor="@color/lightGray"
app:abb_rippleEnabled="true"
app:abb_selectedIndex="0"
app:abb_selectedTabType="text"
app:abb_tabAnimation="slide"
app:abb_tabAnimationSelected="slide"
app:abb_tabColorSelected="@color/white"
app:abb_tabs="@menu/bottom_bar"
app:abb_textAppearance="@style/BottomBarItemText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Creating Record Buttons elements:

<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="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:layout_gravity="bottom">

<LinearLayout
android:id="@+id/front_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="visible"
android:background="@drawable/dialog_bottom_background"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:weightSum="1">

<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_web" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".6"
android:hint="@string/enter_url_address_for_a_video"
android:textColorHint="@color/white"
app:boxStrokeColor="@color/white"
app:boxStrokeWidth="1dp"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:hintTextColor="@color/white">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dialog_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/roboto"
android:inputType="textUri"
android:textColor="@color/white"
android:textColorHint="@color/white" />

</com.google.android.material.textfield.TextInputLayout>

<ImageButton
android:id="@+id/dialog_flip"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/pick_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_flip"/>

<ImageButton
android:id="@+id/dialog_send_url"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/send_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_send" />

</LinearLayout>

<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/darker_light_fuchsia" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:visibility="gone"
android:weightSum="1">

<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_yt_logo_2" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_yt_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".7"
android:hint="@string/enter_yt_url_address_for_a_video"
android:textColorHint="@color/white"
app:boxStrokeColor="@color/white"
app:boxStrokeWidth="1dp"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:hintTextColor="@color/white">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dialog_yt_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/roboto"
android:inputType="textUri"
android:textColor="@color/white"
android:textColorHint="@color/white" />

</com.google.android.material.textfield.TextInputLayout>

<ImageButton
android:id="@+id/dialog_yt_send_url"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/send_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_send" />

</LinearLayout>

<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/darker_light_fuchsia"
android:visibility="gone" />

<LinearLayout
android:id="@+id/dialog_video"
android:layout_width="match_parent"
android:layout_height="70dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:orientation="horizontal"
android:weightSum="1">

<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_video" />

<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".8"
android:fontFamily="@font/roboto"
android:gravity="center_vertical"
android:paddingStart="15dp"
android:paddingEnd="0dp"
android:text="@string/record_video"
android:textColor="@color/white" />

</LinearLayout>

</LinearLayout>

<LinearLayout
android:id="@+id/back_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/dialog_bottom_background"
android:orientation="vertical"
android:padding="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">

<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/close_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="true"
android:contentDescription="@string/close"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_flip"
app:civ_border_color="@color/huawei_red"
app:civ_circle_background_color="@color/huawei_red" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_recycler"
android:layout_width="match_parent"
tools:listitem="@layout/single_video_item"
android:layout_height="160dp"/>

</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

View attachment 5178067

We’ve talked about adding predefined items. Now let’s build the item to create their process for recycler:

<LinearLayout 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="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/colour_orange_bg"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:layout_margin="5dp"
android:weightSum="2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="0dp"
android:layout_weight=".2"
android:layout_gravity="center_vertical"
android:layout_height="32dp"
android:src="@drawable/video_stock" />

<LinearLayout
android:layout_width="0dp"
android:layout_weight="1.5"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/s_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:fontFamily="@font/bree_serif"
android:maxEms="1"
tools:text="Video Name"
android:ellipsize="end"/>

<TextView
android:id="@+id/s_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:fontFamily="@font/phenomena_regular"
android:maxEms="1"
tools:text="Video Url"
android:ellipsize="end"/>

</LinearLayout>

<TextView
android:id="@+id/s_total"
android:layout_width="0dp"
android:layout_weight=".3"
android:textColor="@color/white"
android:fontFamily="@font/phenomena_regular"
android:layout_height="32dp"
android:gravity="center"
android:layout_gravity="center_vertical"
tools:text="00:00"
android:src="@drawable/video_stock" />

</LinearLayout>

Preview of it:

View attachment 5178069

And calling it to their Adapter:

class MainVideoItemAdapter(context: Context, activity: MainActivity) :
RecyclerView.Adapter<MainVideoItemAdapter.ViewHolder>() {

private val act = activity
private val cntx = context
private val dataUrlArray = context.resources.getStringArray(R.array.data_url)
private val dataNameArray = context.resources.getStringArray(R.array.data_name)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.single_video_item, parent, false)
return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindVidItem(dataNameArray[position], dataUrlArray[position])

val scale = act.applicationContext.resources.displayMetrics.density
act.dFrontLyt.cameraDistance = 8000 * scale
act.dBackLyt.cameraDistance = 8000 * scale

val frontAnim =
AnimatorInflater.loadAnimator(
cntx,
R.animator.front_animator
) as AnimatorSet
val backAnim = AnimatorInflater.loadAnimator(
cntx,
R.animator.back_animator
) as AnimatorSet

holder.parent.setOnClickListener {
act.dUrl.setText(dataUrlArray[position])
FlipCard.flipBackAnimator(frontAnim, act.dFrontLyt, backAnim, act.dBackLyt)
}
}

override fun getItemCount(): Int {
return dataUrlArray.size
}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val parent = itemView

var vidName: TextView
var vidUrl: TextView

init {
vidName = parent.findViewById(R.id.s_name)
vidUrl = parent.findViewById(R.id.s_url)
}

fun bindVidItem(vid_name: String, vid_url: String) {
vidName.text = vid_name
vidUrl.text = vid_url
}
}
}

View attachment 5178071

Now for the Main’s interface:

class IMain {
interface ViewMain{
fun setupDialog(context: Context,type:Int)
fun setupRecycler()
fun checkItems()
}
}

Then for its presenter:

class MainPresenter {

fun gotoProfile(context: Context) {
context.startActivity(Intent(context, ProfileActivity::class.java))
}

fun gotoRecord(context: Context) {
context.startActivity(Intent(context, RecordActivity::class.java))
}

fun restart(context: Context) {
context.startActivity(Intent(context, MainActivity::class.java))
}
}

Finally, we can begin for building activity:

class MainActivity : AppCompatActivity(), IMain.ViewMain {

private lateinit var binding: ActivityMainBinding

lateinit var dialog: Dialog
lateinit var dUrl: TextInputEditText

lateinit var dFrontLyt: LinearLayout
lateinit var dBackLyt: LinearLayout

private val presenter: MainPresenter by lazy { MainPresenter() }
…
}

Let’s start by filling overridden methods and custom ones:

setupDialog(…)

override fun setupDialog(context: Context, type: Int) {
dialog = Dialog(context, R.style.BlurTheme)
dialog.window!!.attributes.windowAnimations = type
dialog.setContentView(R.layout.dialog_record)
dialog.setCanceledOnTouchOutside(true)

val dRecord = dialog.findViewById<LinearLayout>(R.id.dialog_video)

val dUrlLyt = dialog.findViewById<TextInputLayout>(R.id.dialog_input_layout)
dUrl = dialog.findViewById(R.id.dialog_url)
val dUrlBtn = dialog.findViewById<ImageButton>(R.id.dialog_send_url)

dFrontLyt = dialog.findViewById(R.id.front_record)
dBackLyt = dialog.findViewById(R.id.back_record)

val circleFlip = dialog.findViewById<CircleImageView>(R.id.close_circle)
val dFlip = dialog.findViewById<ImageButton>(R.id.dialog_flip)

val dRecycler = dialog.findViewById<RecyclerView>(R.id.dialog_recycler)

dRecycler.layoutManager = LinearLayoutManager(this)
dRecycler.adapter = MainVideoItemAdapter(this, this)
dRecycler.setHasFixedSize(true)

val scale = this.applicationContext.resources.displayMetrics.density
dFrontLyt.cameraDistance = 8000 * scale
dBackLyt.cameraDistance = 8000 * scale

val frontAnim =
AnimatorInflater.loadAnimator(
this,
R.animator.front_animator
) as AnimatorSet
val backAnim = AnimatorInflater.loadAnimator(
this,
R.animator.back_animator
) as AnimatorSet

dFlip.setOnClickListener {
FlipCard.flipFrontAnimator(frontAnim, dFrontLyt, backAnim, dBackLyt)
circleFlip.setOnClickListener {
FlipCard.flipBackAnimator(frontAnim, dFrontLyt, backAnim, dBackLyt)
}
}

dUrlBtn.setOnClickListener {
showLogInfo(Constants.mMainActivity, "Passing: " + dUrl.text.toString())
val url = dUrl.text.toString()
if (UrlValidatorHelper.isValidUrl(url))
startActivity(
Intent(this, SinglePlayerActivity::class.java)
.putExtra("url", url)
.putExtra("type", 0)
)
else
dUrlLyt.error = getString(R.string.error_url)
}
dRecord.setOnClickListener {
dialog.dismiss()
presenter.gotoRecord(this)
}

dialog.show()
}

checkItems(), Check if main recycler has items:

override fun checkItems(){ Constants.fSharedRef.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.hasChildren()){
binding.lottieInc.root.visibility = View.GONE
binding.videoRecycler.visibility = View.VISIBLE
} else{
binding.lottieInc.root.visibility = View.VISIBLE
binding.videoRecycler.visibility = View.GONE
}
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity,error.toString())
}
})
}

View attachment 5178077

handle permissions:

private fun requestPermissions() {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.INTERNET,
Manifest.permission.CHANGE_NETWORK_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
2020
)
}

onStart:

override fun onStart() {
super.onStart()
setupRecycler()
checkItems()
}

onCreate():

override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_ExoVideoReference_TransStatusBar)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.hide()
requestPermissions()
binding.animatedBottomBar.setOnTabSelectListener(object :
AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(
lastIndex: Int,
lastTab: AnimatedBottomBar.Tab?,
newIndex: Int,
newTab: AnimatedBottomBar.Tab
) {
when (newIndex) {
/* Home */
0 -> {
presenter.restart(this@MainActivity)
}
/* Record */
1 -> {
setupDialog(this@MainActivity, R.style.BlurTheme)
}
/* Profile */
2 -> {
presenter.gotoProfile(this@MainActivity)
}
}
}

override fun onTabReselected(index: Int, tab: AnimatedBottomBar.Tab) {
super.onTabReselected(index, tab)
when (index) {
0 -> {
presenter.restart(this@MainActivity)
}
1 -> {
setupDialog(this@MainActivity, R.style.BlurTheme)
}
2 -> {
presenter.gotoProfile(this@MainActivity)
}
}
}
})
binding.videoRecycler.layoutManager = LinearLayoutManager(this)
binding.videoRecycler.setHasFixedSize(true)
}

Now I have left setting up recycler to the end because we didn’t create any flavor build bindings yet, but still, I’ll provide it in this section keep in mind that we’ll fill its missing part once we move to Exo Player and HMS Video Kit.

override fun setupRecycler() {
showLogDebug(Constants.mMainActivity, "fSharedRef: ${Constants.fSharedRef}")
val options = FirebaseRecyclerOptions.Builder<DataClass.UploadsShareableDataClass>()
.setQuery(Constants.fSharedRef, DataClass.UploadsShareableDataClass::class.java).build()
val adapterFire = object :
FirebaseRecyclerAdapter<DataClass.UploadsShareableDataClass, HmsGmsVideoViewHolder>(
options
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HmsGmsVideoViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.layout_video, parent, false)
return HmsGmsVideoViewHolder(view)
}

override fun onBindViewHolder(
holder: HmsGmsVideoViewHolder,
position: Int,
model: DataClass.UploadsShareableDataClass
) {
val lisResUid = getRef(position).key
var lovely = model.like.toInt()

holder.sLovely.setOnClickListener {
lovely++
FirebaseDbHelper.getShareItem(lisResUid!!).child("like")
.setValue(lovely.toString())
}

holder.sLovely.text =
getString(R.string.lovely_counter, NumberConvertor.prettyCount(lovely))

FirebaseDbHelper.getPostMessageRef(lisResUid!!)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
holder.bindComments(NumberConvertor.prettyCount(snapshot.childrenCount))
}

override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})

FirebaseDbHelper.getShareItem(lisResUid)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val senderID = snapshot.child("uploaderId").value.toString()
FirebaseDbHelper.getUserInfo(senderID)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val pName = snapshot.child("nameSurname").value.toString()
holder.bindDialogInfo(
model.videoUrl,
pName,
model.uploaderID,
model.like
)
}

override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})
}

override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})

holder.sMessage.setOnClickListener {
startActivity(
Intent(this@MainActivity, PostMessageActivity::class.java)
.putExtra("listID", lisResUid)
)
}
holder.sShare.setOnClickListener { holder.shareUri(model.videoUrl) }
holder.readyPlayer(model.videoUrl, model.videoName)
holder.bindVisibility()
}
}
adapterFire.startListening()
val snapHelper = LinearSnapHelper()
binding.videoRecycler.onFlingListener = null
binding.videoRecycler.clearOnScrollListeners()
snapHelper.attachToRecyclerView(binding.videoRecycler)
binding.videoRecycler.adapter = adapterFire

The final preview of MainActivity

View attachment 5178079

------------------------------

END OF PART - II