Skip to content

Commit

Permalink
Merge branch 'feature/ALTAPPS-1034/shared_customizable_challenges' in…
Browse files Browse the repository at this point in the history
…to feature/ALTAPPS-1035/ios_customizable_challenges
  • Loading branch information
ivan-magda authored Nov 17, 2023
2 parents ca90be0 + 9c376a0 commit 97f2e3c
Show file tree
Hide file tree
Showing 85 changed files with 1,993 additions and 466 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ios_unit_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defaults:

jobs:
test:
if: ${{ vars.IS_IOS_UNIT_TESTING_ENABLED == 'true' }}
name: Run iOS unit tests
runs-on: macos-13
timeout-minutes: 60
Expand Down
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,18 @@
package org.hyperskill.app.android.core.extensions

import android.graphics.drawable.LayerDrawable

class LayerListDrawableDelegate(
private val layerIds: List<Int>,
private val layers: LayerDrawable
) {
fun showLayer(visibleLayerId: Int) {
for (layerId in layerIds) {
val layer = layers.findDrawableByLayerId(layerId).mutate()
layer.alpha =
if (layerId == visibleLayerId) 255 else 0
layers.setDrawableByLayerId(layerId, layer)
layers.invalidateSelf()
}
}
}
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 =
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!")
}
}
}
Loading

0 comments on commit 97f2e3c

Please sign in to comment.