Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALTAPPS-1028: Android share streak #747

Merged
merged 15 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions androidHyperskillApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,15 @@
android:name="firebase_analytics_collection_enabled"
android:value="false" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.hyperskill.app.android.core.extensions

import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.annotation.DrawableRes
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
import org.hyperskill.app.android.BuildConfig
import org.hyperskill.app.android.HyperskillApp

object ShareUtils {
fun getShareDrawableIntent(
context: Context,
@DrawableRes drawableRes: Int,
text: String,
title: String
): Intent {
val shareIntent = Intent().apply {
val bitmap = BitmapFactory.decodeResource(context.resources, drawableRes)

val originImageUri = getUriForBitmap(context, bitmap, "$drawableRes.png")

action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, originImageUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(Intent.EXTRA_TEXT, text)
clipData = ClipData.newRawUri(null, originImageUri)
type = "image/*"
}
return Intent.createChooser(
shareIntent,
title
)
}

private fun getUriForBitmap(
context: Context,
bitmap: Bitmap,
name: String
): Uri {
val file = File(context.cacheDir, name).apply {
createNewFile()
}
FileOutputStream(file).use { stream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
}
return FileProvider.getUriForFile(
HyperskillApp.getAppContext(),
"${BuildConfig.APPLICATION_ID}.provider",
file
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.hyperskill.app.android.share_streak.fragment

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
import coil.transform.RoundedCornersTransformation
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.hyperskill.app.android.R
import org.hyperskill.app.android.databinding.FragmentShareStreakBinding
import org.hyperskill.app.android.view.base.ui.extension.wrapWithTheme
import ru.nobird.android.view.base.ui.extension.argument

class ShareStreakDialogFragment : BottomSheetDialogFragment() {

companion object {
const val TAG = "ShareStreakBottomSheetTag"

fun newInstance(
streak: Int,
@DrawableRes imageRes: Int
): ShareStreakDialogFragment =
ShareStreakDialogFragment().apply {
this.streak = streak
this.imageRes = imageRes
}
}

private var streak: Int by argument()
@get:DrawableRes
private var imageRes: Int by argument()

private val binding: FragmentShareStreakBinding by viewBinding(FragmentShareStreakBinding::bind)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.TopCornersRoundedBottomSheetDialog)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
BottomSheetDialog(requireContext(), theme).also { dialog ->
dialog.setOnShowListener {
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
if (savedInstanceState == null) {
(parentFragment as? Callback)?.onShareStreakBottomSheetShown(streak)
}
}
}

override fun onDismiss(dialog: DialogInterface) {
(parentFragment as? Callback)?.onShareStreakBottomSheetDismissed(streak)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
inflater.wrapWithTheme(requireActivity())
.inflate(R.layout.fragment_share_streak, container, false)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(binding) {
shareStreakImage.load(imageRes) {
transformations(
RoundedCornersTransformation(
requireContext().resources.getDimension(R.dimen.corner_radius)
)
)
}
shareStreakShareButton.setOnClickListener {
(parentFragment as? Callback)?.onShareClick(streak)
}
shareStreakRefuseButton.setOnClickListener {
(parentFragment as? Callback)?.onRefuseStreakSharingClick(streak)
}
}
}

interface Callback {
fun onShareStreakBottomSheetShown(streak: Int)

fun onShareStreakBottomSheetDismissed(streak: Int)

fun onShareClick(streak: Int)

fun onRefuseStreakSharingClick(streak: Int)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.hyperskill.app.android.core.view.ui.fragment.setChildFragment
import org.hyperskill.app.android.core.view.ui.navigation.requireRouter
import org.hyperskill.app.android.databinding.FragmentStageStepWrapperBinding
import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter
import org.hyperskill.app.android.share_streak.fragment.ShareStreakDialogFragment
import org.hyperskill.app.android.step.view.delegate.StepDelegate
import org.hyperskill.app.android.step.view.fragment.StepFragment
import org.hyperskill.app.android.step.view.model.StepCompletionHost
Expand Down Expand Up @@ -41,7 +42,8 @@ class StageStepWrapperFragment :
Fragment(R.layout.fragment_stage_step_wrapper),
ReduxView<StepFeature.State, StepFeature.Action.ViewAction>,
StepCompletionHost,
RequestDailyStudyReminderDialogFragment.Callback {
RequestDailyStudyReminderDialogFragment.Callback,
ShareStreakDialogFragment.Callback {

companion object {
private const val STEP_DESCRIPTION_FRAGMENT_TAG = "step_content"
Expand Down Expand Up @@ -162,4 +164,20 @@ class StageStepWrapperFragment :
override fun onPermissionResult(isGranted: Boolean) {
stepDelegate?.onPermissionResult(isGranted)
}

override fun onShareStreakBottomSheetShown(streak: Int) {
stepViewModel.onShareStreakBottomSheetShown(streak)
}

override fun onShareStreakBottomSheetDismissed(streak: Int) {
stepViewModel.onShareStreakBottomSheetDismissed(streak)
}

override fun onShareClick(streak: Int) {
stepViewModel.onShareClick(streak)
}

override fun onRefuseStreakSharingClick(streak: Int) {
stepViewModel.onRefuseStreakSharingClick(streak)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package org.hyperskill.app.android.step.view.delegate

import android.content.ActivityNotFoundException
import android.content.Context
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import org.hyperskill.app.R
import org.hyperskill.app.android.core.extensions.ShareUtils
import org.hyperskill.app.android.core.extensions.checkNotificationChannelAvailability
import org.hyperskill.app.android.core.view.ui.navigation.requireRouter
import org.hyperskill.app.android.databinding.ErrorNoConnectionWithButtonBinding
Expand All @@ -11,6 +17,7 @@ import org.hyperskill.app.android.main.view.ui.navigation.Tabs
import org.hyperskill.app.android.main.view.ui.navigation.switch
import org.hyperskill.app.android.notification.model.HyperskillNotificationChannel
import org.hyperskill.app.android.notification.permission.NotificationPermissionDelegate
import org.hyperskill.app.android.share_streak.fragment.ShareStreakDialogFragment
import org.hyperskill.app.android.step.view.dialog.TopicPracticeCompletedBottomSheet
import org.hyperskill.app.android.step.view.screen.StepScreen
import org.hyperskill.app.android.step_quiz.view.dialog.CompletedStepOfTheDayDialogFragment
Expand All @@ -25,7 +32,8 @@ class StepDelegate<TFragment>(
private val onRequestDailyStudyRemindersPermissionResult: (Boolean) -> Unit
) : RequestDailyStudyReminderDialogFragment.Callback
where TFragment : Fragment,
TFragment : RequestDailyStudyReminderDialogFragment.Callback {
TFragment : RequestDailyStudyReminderDialogFragment.Callback,
TFragment : ShareStreakDialogFragment.Callback {

private val notificationPermissionDelegate: NotificationPermissionDelegate =
NotificationPermissionDelegate(fragment, ::onNotificationPermissionResult)
Expand Down Expand Up @@ -78,15 +86,22 @@ class StepDelegate<TFragment>(
}
is StepCompletionFeature.Action.ViewAction.ShowProblemOfDaySolvedModal -> {
CompletedStepOfTheDayDialogFragment
.newInstance(earnedGemsText = stepCompletionAction.earnedGemsText)
.newInstance(
earnedGemsText = stepCompletionAction.earnedGemsText,
shareStreakData = stepCompletionAction.shareStreakData
)
.showIfNotExists(fragment.childFragmentManager, CompletedStepOfTheDayDialogFragment.TAG)
}
is StepCompletionFeature.Action.ViewAction.ShowShareStreakModal -> {
// TODO: ALTAPPS-1028 Show share streak modal
ShareStreakDialogFragment
.newInstance(
streak = stepCompletionAction.streak,
imageRes = getShareStreakDrawableRes(stepCompletionAction.streak)
)
.showIfNotExists(fragment.childFragmentManager, ShareStreakDialogFragment.TAG)
}
is StepCompletionFeature.Action.ViewAction.ShowShareStreakSystemModal -> {
// TODO: ALTAPPS-1028 Show system share streak modal (after "Share your streak" button clicked)
// on the problem of day solved modal
shareStreak(stepCompletionAction.streak)
}
}
}
Expand Down Expand Up @@ -121,4 +136,36 @@ class StepDelegate<TFragment>(
.checkNotificationChannelAvailability(context, HyperskillNotificationChannel.DailyReminder)
}
}

@DrawableRes
private fun getShareStreakDrawableRes(streak: Int): Int =
XanderZhu marked this conversation as resolved.
Show resolved Hide resolved
when (streak) {
1 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_1
5 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_5
10 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_10
25 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_25
50 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_50
100 -> org.hyperskill.app.android.R.drawable.img_share_streak_day_100
else -> org.hyperskill.app.android.R.drawable.img_share_streak_day_1
}

private fun getShareStreakText(context: Context): String {
val title = context.getString(R.string.share_streak_sharing_text)
val link = context.getString(R.string.share_streak_sharing_url)
return "$title\n$link"
}

private fun shareStreak(streak: Int) {
val shareIntent = ShareUtils.getShareDrawableIntent(
fragment.requireContext(),
getShareStreakDrawableRes(streak),
text = getShareStreakText(fragment.requireContext()),
title = fragment.requireContext().getString(R.string.share_streak_modal_title)
)
try {
fragment.startActivity(shareIntent)
} catch (e: ActivityNotFoundException) {
Log.e("StepDelegate", "Unable to share streak. Activity not found!")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.hyperskill.app.android.core.extensions.argument
import org.hyperskill.app.android.core.view.ui.fragment.setChildFragment
import org.hyperskill.app.android.databinding.FragmentStepBinding
import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter
import org.hyperskill.app.android.share_streak.fragment.ShareStreakDialogFragment
import org.hyperskill.app.android.step.view.delegate.StepDelegate
import org.hyperskill.app.android.step.view.model.StepCompletionHost
import org.hyperskill.app.android.step.view.model.StepCompletionView
Expand All @@ -30,7 +31,8 @@ class StepFragment :
Fragment(R.layout.fragment_step),
ReduxView<StepFeature.State, StepFeature.Action.ViewAction>,
StepCompletionHost,
RequestDailyStudyReminderDialogFragment.Callback {
RequestDailyStudyReminderDialogFragment.Callback,
ShareStreakDialogFragment.Callback {

companion object {
private const val STEP_TAG = "step"
Expand Down Expand Up @@ -101,6 +103,16 @@ class StepFragment :
}
}

private fun initStepContainer(data: StepFeature.State.Data) {
setChildFragment(R.id.stepContainer, STEP_TAG) {
if (data.step.type == Step.Type.PRACTICE) {
StepPracticeFragment.newInstance(data.step, stepRoute)
} else {
StepTheoryFragment.newInstance(data.step, stepRoute, data.isPracticingAvailable)
}
}
}

override fun onDestroyView() {
super.onDestroyView()
viewStateDelegate = null
Expand All @@ -121,13 +133,19 @@ class StepFragment :
stepDelegate?.onPermissionResult(isGranted)
}

private fun initStepContainer(data: StepFeature.State.Data) {
setChildFragment(R.id.stepContainer, STEP_TAG) {
if (data.step.type == Step.Type.PRACTICE) {
StepPracticeFragment.newInstance(data.step, stepRoute)
} else {
StepTheoryFragment.newInstance(data.step, stepRoute, data.isPracticingAvailable)
}
}
override fun onShareStreakBottomSheetShown(streak: Int) {
stepViewModel.onShareStreakBottomSheetShown(streak)
}

override fun onShareStreakBottomSheetDismissed(streak: Int) {
stepViewModel.onShareStreakBottomSheetDismissed(streak)
}

override fun onRefuseStreakSharingClick(streak: Int) {
stepViewModel.onRefuseStreakSharingClick(streak)
}

override fun onShareClick(streak: Int) {
stepViewModel.onShareClick(streak)
}
}
Loading