Skip to content
Open
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
76 changes: 76 additions & 0 deletions AdManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.example.myapp.presentation.ads

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.example.myapp.domain.model.AdNetworkStrategy

class AdManager(private val context: Context, private val strategy: AdNetworkStrategy) {

private val handler = Handler(Looper.getMainLooper())
private val TAG = "AdManager"

fun initializeAds() {
when (strategy) {
AdNetworkStrategy.IRANIAN_ADS -> Log.d(TAG, "[IRANIAN_ADS] Initializing Iranian Ads SDK (e.g., Tapsell)")
AdNetworkStrategy.GLOBAL_ADS -> Log.d(TAG, "[GLOBAL_ADS] Initializing Global Ads SDK (e.g., AdMob/Google Ad Manager)")
AdNetworkStrategy.UNKNOWN -> Log.d(TAG, "[UNKNOWN] No specific ad strategy, defaulting or waiting.")
}
// Actual SDK initialization would happen here
}

// --- Rewarded Video Ads ---
fun loadRewardedVideoAd(onLoaded: () -> Unit, onFailed: () -> Unit) {
Log.d(TAG, "[$strategy] Loading Rewarded Video Ad...")
// Simulate ad loading delay
handler.postDelayed({
val success = Math.random() > 0.2 // Simulate 80% success rate
if (success) {
Log.d(TAG, "[$strategy] Rewarded Video Ad Loaded.")
onLoaded()
} else {
Log.e(TAG, "[$strategy] Rewarded Video Ad Failed to Load.")
onFailed()
}
}, 2000) // 2-second delay
}

fun showRewardedVideoAd(onRewarded: (rewardAmount: Int) -> Unit, onClosed: () -> Unit) {
Log.d(TAG, "[$strategy] Showing Rewarded Video Ad...")
// Simulate ad showing and reward
handler.postDelayed({
val rewardAmount = 100 // Dummy reward
Log.d(TAG, "[$strategy] Rewarded Video Ad: User Rewarded with $rewardAmount coins.")
onRewarded(rewardAmount)

handler.postDelayed({
Log.d(TAG, "[$strategy] Rewarded Video Ad Closed.")
onClosed()
}, 500) // Short delay for closing
}, 1500) // 1.5-second delay for ad "viewing"
}

// --- Interstitial Ads ---
fun loadInterstitialAd(onLoaded: () -> Unit, onFailed: () -> Unit) {
Log.d(TAG, "[$strategy] Loading Interstitial Ad...")
handler.postDelayed({
val success = Math.random() > 0.2 // Simulate 80% success rate
if (success) {
Log.d(TAG, "[$strategy] Interstitial Ad Loaded.")
onLoaded()
} else {
Log.e(TAG, "[$strategy] Interstitial Ad Failed to Load.")
onFailed()
}
}, 2000) // 2-second delay
}

fun showInterstitialAd(onClosed: () -> Unit) {
Log.d(TAG, "[$strategy] Showing Interstitial Ad...")
handler.postDelayed({
Log.d(TAG, "[$strategy] Interstitial Ad Shown and Closed.")
onClosed()
}, 1500) // 1.5-second delay
}
}
30 changes: 30 additions & 0 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapp">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait"> {/* Requirement: Portrait-only mode */}
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
23 changes: 23 additions & 0 deletions Particle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.myapp.presentation.model

import android.graphics.Color

data class Particle(
var x: Float,
var y: Float,
var color: Int,
var radius: Float,
var xSpeed: Float,
var ySpeed: Float,
var alpha: Int = 255,
var lifetime: Int // e.g., in frames or milliseconds
) {
companion object {
// Predefined rainbow colors for Rainbow Burst effect
val RAINBOW_COLORS = listOf(
Color.RED, Color.rgb(255, 165, 0), Color.YELLOW, // Orange
Color.GREEN, Color.BLUE, Color.rgb(75, 0, 130), // Indigo
Color.rgb(128, 0, 128) // Violet
)
}
}
13 changes: 13 additions & 0 deletions Skin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.myapp.domain.model

import androidx.annotation.DrawableRes

data class Skin(
val id: String,
val name: String,
@DrawableRes val iconResId: Int, // For displaying the skin in a list
var isUnlocked: Boolean,
val unlockCost: Int, // 0 if unlocked by default or through non-coin means
// Potentially, add fields for actual visual assets if not just a color tint
// e.g., val mainColor: Int? = null, val detailColor: Int? = null, val spriteName: String? = null
)
89 changes: 89 additions & 0 deletions SkinAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.example.myapp.presentation.ui

import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.myapp.R
import com.example.myapp.domain.model.Skin

class SkinAdapter(
private val onUnlockClicked: (skin: Skin) -> Unit,
private val onSelectClicked: (skin: Skin) -> Unit
) : ListAdapter<Skin, SkinAdapter.SkinViewHolder>(SkinDiffCallback()) {

private var currentCoins: Int = 0
private var selectedSkinId: String? = null

fun setCurrentCoins(coins: Int) {
currentCoins = coins
// notifyDataSetChanged() // Could be inefficient, better to handle in bind or use payloads
}

fun setSelectedSkinId(skinId: String?) {
selectedSkinId = skinId
notifyDataSetChanged() // Necessary to update selection highlights
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SkinViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_skin, parent, false) // item_skin.xml needs to be created
return SkinViewHolder(view)
}

override fun onBindViewHolder(holder: SkinViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, currentCoins, selectedSkinId == item.id, onUnlockClicked, onSelectClicked)
}

class SkinViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nameTextView: TextView = itemView.findViewById(R.id.skinNameTextView)
private val iconImageView: ImageView = itemView.findViewById(R.id.skinIconImageView)
private val actionButton: Button = itemView.findViewById(R.id.skinActionButton)
private val itemLayout: View = itemView // For background tinting if selected

fun bind(
skin: Skin,
currentCoins: Int,
isSelected: Boolean,
onUnlockClicked: (skin: Skin) -> Unit,
onSelectClicked: (skin: Skin) -> Unit
) {
nameTextView.text = skin.name
iconImageView.setImageResource(skin.iconResId) // Placeholder icons

if (isSelected) {
itemLayout.setBackgroundColor(Color.LTGRAY) // Highlight selected
actionButton.text = "Selected"
actionButton.isEnabled = false
} else {
itemLayout.setBackgroundColor(Color.TRANSPARENT) // Default background
if (skin.isUnlocked) {
actionButton.text = "Select"
actionButton.isEnabled = true
actionButton.setOnClickListener { onSelectClicked(skin) }
} else {
actionButton.text = "Unlock (${skin.unlockCost})"
actionButton.isEnabled = currentCoins >= skin.unlockCost
actionButton.setOnClickListener { onUnlockClicked(skin) }
}
}
}
}

class SkinDiffCallback : DiffUtil.ItemCallback<Skin>() {
override fun areItemsTheSame(oldItem: Skin, newItem: Skin): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Skin, newItem: Skin): Boolean {
return oldItem == newItem
}
}
}
120 changes: 120 additions & 0 deletions SkinsActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.example.myapp.presentation.ui

import android.media.AudioAttributes
import android.media.SoundPool
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.myapp.R
import com.example.myapp.domain.model.Skin

class SkinsActivity : AppCompatActivity() {

private lateinit var viewModel: MainViewModel
private lateinit var skinsRecyclerView: RecyclerView
private lateinit var skinAdapter: SkinAdapter // To be created
private lateinit var currentCoinsTextView: TextView
private lateinit var backButton: Button

private var soundPool: SoundPool? = null
private var buttonClickSoundId: Int = 0
private var itemUnlockedSoundId: Int = 0


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_skins)

viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

currentCoinsTextView = findViewById(R.id.currentCoinsTextViewSkins)
backButton = findViewById(R.id.backButtonSkins)

setupSoundPool()
loadSounds()
setupRecyclerView()
observeViewModel()

backButton.setOnClickListener {
playSound(buttonClickSoundId)
finish()
}
}

private fun setupRecyclerView() {
skinsRecyclerView = findViewById(R.id.skinsRecyclerView)
skinAdapter = SkinAdapter(
onUnlockClicked = { skin ->
playSound(buttonClickSoundId)
if (viewModel.unlockSkin(skin.id)) {
playSound(itemUnlockedSoundId)
Toast.makeText(this, "${skin.name} Unlocked!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Not enough coins or already unlocked!", Toast.LENGTH_SHORT).show()
}
},
onSelectClicked = { skin ->
playSound(buttonClickSoundId)
viewModel.selectSkin(skin.id)
Toast.makeText(this, "${skin.name} Selected!", Toast.LENGTH_SHORT).show()
// Adapter will update based on LiveData observation for selected state
}
)
skinsRecyclerView.adapter = skinAdapter
// Using GridLayoutManager for a more skin-selection friendly UI
skinsRecyclerView.layoutManager = GridLayoutManager(this, 3) // 3 items per row
}

private fun observeViewModel() {
viewModel.objectSkins.observe(this, Observer { skins ->
skinAdapter.submitList(skins)
})

viewModel.currentCoins.observe(this, Observer { coins ->
currentCoinsTextView.text = "Coins: $coins"
skinAdapter.setCurrentCoins(coins)
// Could call notifyDataSetChanged or more specific updates if needed for button states
})

viewModel.selectedSkinId.observe(this, Observer { selectedId ->
skinAdapter.setSelectedSkinId(selectedId)
// Adapter needs to handle highlighting the selected item
})
}

private fun setupSoundPool() {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
soundPool = SoundPool.Builder()
.setMaxStreams(2)
.setAudioAttributes(audioAttributes)
.build()
}

private fun loadSounds() {
soundPool?.let {
buttonClickSoundId = it.load(this, R.raw.button_click, 1)
itemUnlockedSoundId = it.load(this, R.raw.upgrade_purchased, 1) // Re-use upgrade sound for unlock
}
}

private fun playSound(soundId: Int) {
if (soundId > 0) {
soundPool?.play(soundId, 0.8f, 0.8f, 1, 0, 1f)
}
}

override fun onDestroy() {
super.onDestroy()
soundPool?.release()
soundPool = null
}
}
Loading