4 |
5 | # PlaySho: Virtual Cinema for Shared Movie Nights
6 |
7 | Turn off those movie nights into immersive cinematic experiences with PlaySho! 🎬 You can watch the same video with your friends from anywhere. Create a virtual cinema, share a streaming link and enjoy synchronized playback to have unforgettable experience of watching a movie.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## 📱 Server Side
18 | For the Server Side of PlaySho, please visit the [PlaySho Nestjs repository](https://github.com/binaryb3ast/playsho-nest).
19 |
20 | ## 🚀 Key Features:
21 |
22 | ### 🎥 Virtual Cinema:
23 |
24 | Set up your own private cinema room and have a shared movie night with friends.
25 |
26 | ### 🍿 Single Video Playback:
27 |
28 | For that cinema-like feel, you can stream one video synchronously.
29 |
30 | ### 🔗 Stream Link Sharing:
31 |
32 | Create a special stream link and invite your friends to join in your virtual theatre.
33 |
34 | ### 💬 Real-Time Interaction:
35 |
36 | Chatting and sharing reactions among friends gives an opportunity to watch movies socially.
37 |
38 | ### 📱 Cross-Platform Accessibility:
39 |
40 | Use PlaySho on different gadgets, be it smartphone, tablet or desktop computer to have an adjustable and joyful movie night.
41 |
42 | ### 🌐 Stay Connected:
43 |
44 | Have common movie minutes with friends and family who are in different geographical locations.
45 |
46 | ## 🛠️ How It Works:
47 |
48 | 1. **Create Cinema Room:**
49 | - Set up a virtual cinema room that can be altered to suit the right mood for a perfect movie night.
50 |
51 | 2. **Choose Your Movie:**
52 | - Choose one video that will be enjoyed by all people together.
53 |
54 | 3. **Generate Stream Link:**
55 | - Share a unique streaming link with your buddies.
56 |
57 | 4. **Watch Together:**
58 | - Experience the magic of synchronized playback as you watch a single video together in real-time.
59 |
60 | ## 🌟 Why PlaySho:
61 |
62 | - **Virtual Movie Nights:**
63 | - Make shared film nights feel like you are actually at the theatre or cinema hall
64 |
65 | - **Synchronized Playback:**
66 | - Have perfect timing and synchronization that is characteristic of watching movies from cinemas
67 |
68 | - **No Limits:**
69 | - Friends might be watching from anywhere making distances irrelevant
70 |
71 | ## 🎉 Turn your virtual cinema dreams into reality with PlaySho. Download now and start hosting unforgettable shared movie nights! 🍿✨
72 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "com.playsho.android"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | applicationId = "com.playsho.android"
12 | minSdk = 24
13 | targetSdk = 34
14 | versionCode = 1
15 | versionName = "1.0"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 |
21 |
22 | kotlinOptions {
23 | jvmTarget = "1.8"
24 | }
25 |
26 |
27 | buildFeatures {
28 | dataBinding = true
29 | viewBinding = true
30 |
31 | }
32 |
33 | buildTypes {
34 | release {
35 | isMinifyEnabled = false
36 | proguardFiles(
37 | getDefaultProguardFile("proguard-android-optimize.txt"),
38 | "proguard-rules.pro"
39 | )
40 | }
41 | }
42 | packaging {
43 | resources {
44 | excludes +="META-INF/rxjava.properties"
45 | }
46 | }
47 | compileOptions {
48 | sourceCompatibility = JavaVersion.VERSION_1_8
49 | targetCompatibility = JavaVersion.VERSION_1_8
50 | }
51 | }
52 |
53 | dependencies {
54 |
55 | implementation(libs.androidx.appcompat)
56 | implementation(libs.material)
57 | implementation(libs.androidx.constraintlayout)
58 | implementation(libs.androidx.core.ktx)
59 | testImplementation(libs.junit)
60 | androidTestImplementation(libs.androidx.junit)
61 | androidTestImplementation(libs.androidx.espresso.core)
62 | implementation("androidx.core:core-splashscreen:1.1.0-rc01")
63 |
64 | implementation("com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0")
65 | implementation("com.squareup.retrofit2:adapter-rxjava:2.9.0")
66 | implementation("com.squareup.retrofit2:retrofit:2.9.0")
67 | implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
68 | implementation("com.squareup.retrofit2:converter-gson:2.9.0")
69 | implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.12")
70 | implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12")
71 |
72 | implementation("androidx.datastore:datastore-preferences:1.0.0")
73 |
74 | implementation("androidx.media3:media3-exoplayer:1.2.1")
75 | implementation("androidx.media3:media3-ui:1.2.1")
76 | implementation("androidx.media3:media3-exoplayer-dash:1.2.1")
77 |
78 | implementation("com.squareup.picasso:picasso:2.71828")
79 |
80 | implementation("com.github.razir.progressbutton:progressbutton:2.1.0")
81 |
82 | implementation("androidx.annotation:annotation-jvm:1.7.1")
83 |
84 | implementation("io.socket:socket.io-client:2.0.0") {
85 | exclude(group = "org.json", module = "json")
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/playsho/android/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.playsho.android;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("com.playsho.android", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
22 |
23 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
61 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/roboto_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/assets/fonts/roboto_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/roboto_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/assets/fonts/roboto_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/roboto_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/assets/fonts/roboto_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/adapter/MessageAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.adapter
2 |
3 | import android.graphics.Color
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.ViewGroup
8 | import android.view.animation.AlphaAnimation
9 | import android.view.animation.Animation
10 | import android.view.animation.Animation.AnimationListener
11 | import android.view.animation.TranslateAnimation
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.playsho.android.data.Message
14 | import com.playsho.android.databinding.ItemMessageMeBinding
15 | import com.playsho.android.databinding.ItemMessageSenderBinding
16 | import com.playsho.android.databinding.ItemMessageSystemBinding
17 | import com.playsho.android.utils.AnimationHelper
18 | import com.playsho.android.utils.accountmanager.AccountInstance
19 |
20 |
21 | class MessageAdapter(private val dataSet: MutableList) :
22 | RecyclerView.Adapter() {
23 |
24 | companion object {
25 | const val SENDER = 2
26 | const val SYSTEM = 0
27 | const val ME = 1
28 | }
29 |
30 | override fun getItemCount(): Int {
31 | return dataSet.size
32 | }
33 |
34 | override fun getItemViewType(position: Int): Int {
35 | return if (dataSet[position].type == "system") {
36 | SYSTEM
37 | } else if (dataSet[position].sender.tag == AccountInstance.getUserData("tag")) {//me
38 | SENDER
39 | } else {
40 | SENDER
41 | }
42 | }
43 |
44 | // Create new views (invoked by the layout manager)
45 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
46 | return when (viewType) {
47 | SENDER -> {
48 | val inflater = LayoutInflater.from(parent.context)
49 | val binding = ItemMessageSenderBinding.inflate(inflater, parent, false)
50 | SenderViewHolder(binding)
51 | }
52 |
53 | ME -> {
54 | val inflater = LayoutInflater.from(parent.context)
55 | val binding = ItemMessageMeBinding.inflate(inflater, parent, false)
56 | MyselfViewHolder(binding)
57 | }
58 |
59 | else -> {
60 | val inflater = LayoutInflater.from(parent.context)
61 | val binding = ItemMessageSystemBinding.inflate(inflater, parent, false)
62 | SystemViewHolder(binding)
63 | }
64 | }
65 | }
66 |
67 | fun addMessage(message: Message) {
68 | dataSet.add(0, message)
69 | notifyItemInserted(0)
70 | }
71 |
72 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
73 | val message = dataSet[position]
74 | AnimationHelper.slideInFromBottom(holder.itemView , AnimationHelper.Duration.SLIDE_IN_FROM_BOTTOM)
75 | when (holder) {
76 | is SenderViewHolder -> holder.bind(message)
77 | is MyselfViewHolder -> holder.bind(message)
78 | is SystemViewHolder -> holder.bind(message)
79 | }
80 | Handler(Looper.getMainLooper()).postDelayed({
81 | dataSet.remove(message)
82 | notifyItemRemoved(holder.bindingAdapterPosition)
83 | }, AnimationHelper.Duration.DISAPPEAR_MESSAGE) // 5 seconds delay
84 | }
85 |
86 |
87 | inner class SenderViewHolder(private val binding: ItemMessageSenderBinding ) :
88 | RecyclerView.ViewHolder(binding.root) {
89 | fun bind(message: Message) {
90 | binding.apply {
91 | message.also {
92 | txtMessage.text = message.message
93 | txtName.text = message.sender.userName
94 | txtName.setTextColor(Color.parseColor(message.sender.color))
95 | }
96 |
97 | }
98 | }
99 | }
100 |
101 | inner class SystemViewHolder(private val binding: ItemMessageSystemBinding) :
102 | RecyclerView.ViewHolder(binding.root) {
103 | fun bind(message: Message) {
104 | binding.apply {
105 | message.also {
106 | txtMessage.text = message.message
107 | }
108 |
109 | }
110 | }
111 | }
112 |
113 | inner class MyselfViewHolder(private val binding: ItemMessageMeBinding) :
114 | RecyclerView.ViewHolder(binding.root) {
115 | fun bind(message: Message) {
116 | binding.apply {
117 | message.also {
118 | txtMessage.text = message.message
119 | }
120 | }
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/base/ApplicationLoader.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.base
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.res.Configuration
6 | import androidx.appcompat.app.AppCompatDelegate
7 | import com.google.gson.Gson
8 | import com.playsho.android.db.SessionStorage
9 | import com.playsho.android.utils.DimensionUtils
10 |
11 | /**
12 | * Custom Application class that initializes the application context, Gson, and SessionStorage.
13 | */
14 | class ApplicationLoader : Application() {
15 |
16 | companion object {
17 | lateinit var context: Context
18 | private lateinit var gson: Gson
19 | private lateinit var sessionStorage: SessionStorage
20 |
21 |
22 | /**
23 | * Gets the SessionStorage instance for managing session storage.
24 | *
25 | * @return The SessionStorage instance.
26 | */
27 | @JvmStatic
28 | fun getSessionStorage(): SessionStorage {
29 | return sessionStorage
30 | }
31 | }
32 |
33 | /**
34 | * Called when the application is first created. Initializes the application context, Gson, and SessionStorage.
35 | */
36 | override fun onCreate() {
37 | super.onCreate()
38 | context = applicationContext
39 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
40 | gson = Gson()
41 | sessionStorage = SessionStorage()
42 | try {
43 | DimensionUtils.checkDisplaySize(context, null)
44 | } catch (e: Exception) {
45 | e.printStackTrace()
46 | }
47 | }
48 |
49 | /**
50 | * Called when the configuration of the device changes.
51 | *
52 | * @param newConfig The new configuration.
53 | */
54 | override fun onConfigurationChanged(newConfig: Configuration) {
55 | super.onConfigurationChanged(newConfig)
56 | try {
57 | DimensionUtils.checkDisplaySize(context, newConfig)
58 | } catch (e: Exception) {
59 | e.printStackTrace()
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.base
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.net.ConnectivityManager
7 | import android.os.Bundle
8 | import androidx.activity.OnBackPressedCallback
9 | import androidx.activity.result.ActivityResult
10 | import androidx.annotation.ColorRes
11 | import androidx.annotation.LayoutRes
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.appcompat.app.AppCompatDelegate
14 | import androidx.databinding.DataBindingUtil
15 | import androidx.databinding.ViewDataBinding
16 | import com.playsho.android.component.ActivityLauncher
17 | import com.playsho.android.db.SessionStorage
18 | import com.playsho.android.utils.NetworkListener
19 | import com.playsho.android.utils.SystemUtilities
20 | import java.util.concurrent.atomic.AtomicBoolean
21 |
22 | /**
23 | * A base activity class that provides common functionality and structure for other activities.
24 | *
25 | * @param B The type of ViewDataBinding used by the activity.
26 | */
27 | abstract class BaseActivity : AppCompatActivity() {
28 |
29 | // Flag to enable or disable the custom back press callback
30 | private var isBackPressCallbackEnable = false
31 |
32 | // View binding instance
33 | protected lateinit var binding: B
34 | protected lateinit var networkListener: NetworkListener
35 | // Activity launcher instance
36 | protected val activityLauncher = ActivityLauncher.registerActivityForResult(this)
37 |
38 | // Tag for logging
39 | protected open val TAG: String = javaClass.simpleName
40 |
41 | /**
42 | * Abstract method to get the layout resource ID for the activity.
43 | *
44 | * @return The layout resource ID.
45 | */
46 | protected abstract fun getLayoutResourceId(): Int
47 |
48 | /**
49 | * Abstract method to handle custom behavior on back press.
50 | */
51 | protected abstract fun onBackPress()
52 |
53 | /**
54 | * Gets a String extra from the Intent.
55 | *
56 | * @param key The key of the extra.
57 | * @return The String extra value.
58 | */
59 | protected fun getIntentStringExtra(key: String): String? {
60 | return intent.getStringExtra(key)
61 | }
62 |
63 | /**
64 | * Gets the name of the current activity.
65 | *
66 | * @return The name of the activity.
67 | */
68 | protected fun getClassName(): String {
69 | return javaClass.simpleName
70 | }
71 |
72 | override fun onCreate(savedInstanceState: Bundle?) {
73 | super.onCreate(savedInstanceState)
74 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
75 | binding = DataBindingUtil.setContentView(this, getLayoutResourceId())
76 | networkListener = NetworkListener()
77 | onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
78 | }
79 |
80 | override fun onDestroy() {
81 | super.onDestroy()
82 | onBackPressedCallback.remove()
83 | }
84 |
85 | private val onBackPressedCallback = object : OnBackPressedCallback(isBackPressCallbackEnable) {
86 | override fun handleOnBackPressed() {
87 | // Call the abstract method for custom back press handling
88 | onBackPress()
89 | }
90 | }
91 |
92 | /**
93 | * Sets whether the custom back press callback is enabled or disabled.
94 | *
95 | * @param isBackPressCallbackEnable true to enable the callback, false to disable it.
96 | */
97 | fun setBackPressedCallBackEnable(isBackPressCallbackEnable: Boolean) {
98 | this.isBackPressCallbackEnable = isBackPressCallbackEnable
99 | }
100 |
101 | /**
102 | * Sets the status bar color using the SystemUtilities class.
103 | *
104 | * @param color The color resource ID.
105 | * @param isDark true if the status bar icons should be dark, false otherwise.
106 | */
107 | protected fun setStatusBarColor(@ColorRes color: Int, isDark: Boolean) {
108 | SystemUtilities.changeStatusBarBackgroundColor(activity(), color, isDark)
109 | }
110 |
111 | /**
112 | * Gets the context of the activity.
113 | *
114 | * @return The context of the activity.
115 | */
116 | protected fun context(): Context {
117 | return this
118 | }
119 |
120 | /**
121 | * Gets the activity instance.
122 | *
123 | * @return The activity instance.
124 | */
125 | protected fun activity(): Activity {
126 | return this
127 | }
128 |
129 | /**
130 | * Gets the SessionStorage instance from the ApplicationLoader.
131 | *
132 | * @return The SessionStorage instance.
133 | */
134 | protected fun getSessionManager(): SessionStorage {
135 | return ApplicationLoader.getSessionStorage()
136 | }
137 |
138 | /**
139 | * Checks if the network is available using the NetworkListener.
140 | *
141 | * @return true if the network is available, false otherwise.
142 | */
143 | protected fun isNetworkAvailable(): AtomicBoolean {
144 | return networkListener.isNetworkAvailable
145 | }
146 |
147 | override fun onResume() {
148 | super.onResume()
149 | val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
150 | connectivityManager.registerDefaultNetworkCallback(networkListener)
151 | }
152 |
153 | override fun onPause() {
154 | super.onPause()
155 | val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
156 | connectivityManager.unregisterNetworkCallback(networkListener)
157 | }
158 |
159 | protected inline fun Activity.openActivity(vararg params: Pair) {
160 | val intent = createIntent().apply {
161 | for (param in params) {
162 | val (key, value) = param
163 | when (value) {
164 | is String -> putExtra(key, value)
165 | is Int -> putExtra(key, value)
166 | // Add more cases for other data types as needed
167 | }
168 | }
169 | }
170 | startActivity(intent)
171 | }
172 |
173 | protected inline fun Context.createIntent() =
174 | Intent(this, T::class.java)
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/base/BaseBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.base
2 |
3 | import android.content.DialogInterface
4 | import android.os.Bundle
5 | import android.support.annotation.Nullable
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.coordinatorlayout.widget.CoordinatorLayout
10 | import androidx.databinding.DataBindingUtil
11 | import androidx.databinding.ViewDataBinding
12 | import com.google.android.material.bottomsheet.BottomSheetBehavior
13 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
14 | import com.playsho.android.R
15 | import com.playsho.android.db.SessionStorage
16 |
17 | abstract class BaseBottomSheet : BottomSheetDialogFragment() {
18 |
19 | protected lateinit var binding: B
20 | private var origin: String? = null
21 | protected var bottomSheetStatusCallback: BottomSheetStatusCallback? = null
22 | protected var bottomSheetResultCallback: BottomSheetResultCallback? = null
23 | private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>
24 |
25 | interface BottomSheetStatusCallback {
26 | fun onBottomSheetShow()
27 | fun onBottomSheetDismiss()
28 | }
29 |
30 | interface BottomSheetResultCallback {
31 | fun onBottomSheetProcessSuccess(data: String)
32 | fun onBottomSheetProcessFail(data: String)
33 | }
34 |
35 | protected abstract fun getLayoutResourceId(): Int
36 | protected abstract fun initView()
37 |
38 | init {
39 | val parentFragment = parentFragment
40 | setOrigin(parentFragment?.javaClass?.simpleName ?: activity?.javaClass?.simpleName ?: "")
41 | }
42 |
43 | fun setStatusCallback(bottomSheetCallback: BottomSheetStatusCallback) {
44 | this.bottomSheetStatusCallback = bottomSheetCallback
45 | }
46 |
47 | fun setResultCallback(callback: BottomSheetResultCallback) {
48 | this.bottomSheetResultCallback = callback
49 | }
50 |
51 | protected fun setOrigin(origin: String) {
52 | this.origin = origin
53 | }
54 |
55 | protected fun getOrigin(): String? {
56 | return this.origin
57 | }
58 |
59 | @Nullable
60 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
61 | binding = DataBindingUtil.inflate(inflater, getLayoutResourceId(), container, false)
62 | return binding.root
63 | }
64 |
65 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
66 | super.onViewCreated(view, savedInstanceState)
67 |
68 | }
69 |
70 | protected fun getSessionStorage(): SessionStorage {
71 | return ApplicationLoader.getSessionStorage()
72 | }
73 |
74 | fun show() {
75 | show(parentFragmentManager, getOrigin())
76 | }
77 |
78 | override fun getTheme(): Int {
79 | return R.style.AppBottomSheetDialogTheme
80 | }
81 |
82 | override fun onDismiss(dialog: DialogInterface) {
83 | super.onDismiss(dialog)
84 | bottomSheetStatusCallback?.onBottomSheetDismiss()
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/base/BaseModel.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.base
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import org.jetbrains.annotations.NotNull
5 | import java.io.Serializable
6 | import java.util.UUID
7 |
8 | /**
9 | * Base model class for common attributes shared among other model classes.
10 | */
11 | class BaseModel : Serializable {
12 | var hash: String = UUID.randomUUID().toString()
13 |
14 | @SerializedName("code")
15 | var code: String? = null
16 |
17 | @SerializedName("_id")
18 | var id: String? = null
19 |
20 | @SerializedName("created_at")
21 | var createdAt: String? = null
22 |
23 | @SerializedName("updated_at")
24 | var updatedAt: String? = null
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/base/BasePopup.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.base
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.ColorDrawable
5 | import android.transition.Transition
6 | import android.util.AttributeSet
7 | import android.view.LayoutInflater
8 | import android.view.MotionEvent
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.widget.PopupWindow
12 | import androidx.annotation.Nullable
13 | import androidx.databinding.DataBindingUtil
14 | import androidx.databinding.ViewDataBinding
15 |
16 | /**
17 | * BasePopup is an abstract class that provides a foundation for creating custom popup windows in Android applications.
18 | * It extends the PopupWindow class and includes common functionalities for handling popups.
19 | * To use this class, you need to extend it and implement the required methods.
20 | * Usage Example:
21 | * ```kotlin
22 | * class CustomPopup : BasePopup() {
23 | *
24 | * override fun getLayoutResourceId(): Int {
25 | * // Return the layout resource ID for your custom popup layout
26 | * return R.layout.your_popup_layout
27 | * }
28 | *
29 | * override fun onViewInitialized() {
30 | * // Initialize your views and set any necessary listeners
31 | * // This method is called after inflating the view and before showing the popup
32 | * }
33 | * }
34 | * ```
35 | * Features:
36 | * - Supports data binding with ViewDataBinding.
37 | * - Provides methods for showing the popup at a specified location or at the center of an anchor view.
38 | * - Allows customization of enter transition.
39 | *
40 | * @param T The type of ViewDataBinding used in the popup.
41 | */
42 | abstract class BasePopup(context: Context) : PopupWindow(context) {
43 |
44 | /**
45 | * The ViewDataBinding instance associated with the popup layout.
46 | */
47 | protected lateinit var binding: T
48 |
49 | /**
50 | * Inflates the popup view and performs necessary setup.
51 | */
52 | private fun inflateView(context: Context) {
53 | val inflater = LayoutInflater.from(context)
54 | binding = DataBindingUtil.inflate(inflater, getLayoutResourceId(), null, false)
55 | setContentView(binding.root)
56 | setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT))
57 |
58 | width = ViewGroup.LayoutParams.WRAP_CONTENT
59 | height = ViewGroup.LayoutParams.WRAP_CONTENT
60 | isOutsideTouchable = true
61 |
62 | // Custom initialization for the view
63 | onViewInitialized()
64 | }
65 |
66 |
67 | /**
68 | * Abstract method to get the layout resource ID for the popup.
69 | *
70 | * @return The layout resource ID.
71 | */
72 | protected abstract fun getLayoutResourceId(): Int
73 |
74 | /**
75 | * Abstract method called after inflating the view and before showing the popup.
76 | * Use this method to initialize views and set any necessary listeners.
77 | */
78 | protected abstract fun onViewInitialized()
79 |
80 | /**
81 | * Shows the popup at the specified location, triggered by a MotionEvent.
82 | *
83 | * @param anchor The anchor view.
84 | * @param event The motion event that triggered the popup.
85 | */
86 | fun showAtLocation(anchor: View, event: MotionEvent) {
87 | // Implementation for showing the popup at a specific location
88 | }
89 |
90 | /**
91 | * Shows the popup at the center of the anchor view.
92 | *
93 | * @param anchor The anchor view.
94 | */
95 | fun showAtLocation(anchor: View) {
96 | val location = IntArray(2)
97 | anchor.getLocationOnScreen(location)
98 | val x = location[0] + anchor.width / 2
99 | val y = location[1] + anchor.height / 2
100 | showAtLocation(anchor, 0, x, y)
101 | }
102 |
103 | init {
104 | inflateView(context)
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/component/ActivityLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.component
2 |
3 | import android.content.Intent
4 | import androidx.activity.result.ActivityResult
5 | import androidx.activity.result.ActivityResultCaller
6 | import androidx.activity.result.ActivityResultLauncher
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import androidx.activity.result.contract.ActivityResultContracts
9 | import androidx.annotation.NonNull
10 | import androidx.annotation.Nullable
11 |
12 | class ActivityLauncher private constructor(
13 | private val launcher: ActivityResultLauncher,
14 | private var onActivityResult: OnActivityResult?
15 | ) {
16 | interface OnActivityResult {
17 | fun onActivityResult(result: O)
18 | }
19 |
20 | companion object {
21 | @NonNull
22 | @JvmStatic
23 | fun registerForActivityResult(
24 | caller: ActivityResultCaller,
25 | contract: ActivityResultContract,
26 | onActivityResult: OnActivityResult?
27 | ): ActivityLauncher {
28 | return ActivityLauncher(caller.registerForActivityResult(contract) {
29 | onActivityResult?.onActivityResult(it)
30 | }, onActivityResult)
31 | }
32 |
33 | @NonNull
34 | @JvmStatic
35 | fun registerForActivityResult(
36 | caller: ActivityResultCaller,
37 | contract: ActivityResultContract
38 | ): ActivityLauncher {
39 | return registerForActivityResult(caller, contract, null)
40 | }
41 |
42 | @NonNull
43 | @JvmStatic
44 | fun registerActivityForResult(caller: ActivityResultCaller): ActivityLauncher {
45 | return registerForActivityResult(
46 | caller,
47 | ActivityResultContracts.StartActivityForResult()
48 | )
49 | }
50 | }
51 |
52 | fun setOnActivityResult(onActivityResult: OnActivityResult?) {
53 | this.onActivityResult = onActivityResult
54 | }
55 |
56 | fun launch(input: Input, onActivityResult: OnActivityResult?) {
57 | this.onActivityResult = onActivityResult
58 | launcher.launch(input)
59 | }
60 |
61 | fun launch(input: Input) {
62 | launch(input, onActivityResult)
63 | }
64 |
65 | private fun callOnActivityResult(result: Result) {
66 | onActivityResult?.onActivityResult(result)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/component/Button.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.component
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.Color
6 | import android.graphics.drawable.GradientDrawable
7 | import android.os.Build
8 | import android.util.AttributeSet
9 | import android.view.Gravity
10 | import androidx.appcompat.widget.AppCompatButton
11 | import androidx.appcompat.widget.AppCompatImageView
12 | import androidx.core.widget.ImageViewCompat
13 | import com.playsho.android.R
14 | import com.github.razir.progressbutton.DrawableButton
15 | import com.github.razir.progressbutton.attachTextChangeAnimator
16 | import com.github.razir.progressbutton.hideProgress
17 | import com.github.razir.progressbutton.showProgress
18 | import com.playsho.android.utils.LocalController
19 | import com.playsho.android.utils.ThemeHelper
20 |
21 | class Button @JvmOverloads constructor(
22 | context: Context,
23 | attrs: AttributeSet? = null,
24 | defStyleAttr: Int = 0
25 | ) : AppCompatButton(context, attrs, defStyleAttr) {
26 |
27 | private var mode: ButtonMode = ButtonMode.PRIMARY;
28 | private var isClickDisable: Boolean = false;
29 | private var text: String = ""
30 |
31 | enum class ButtonMode {
32 | PRIMARY,
33 | SECONDARY
34 | }
35 |
36 | init {
37 | if (!isInEditMode) {
38 | val a: TypedArray =
39 | context.obtainStyledAttributes(attrs, R.styleable.Button, defStyleAttr, 0)
40 | mode = when (a.getString(R.styleable.Button_mode) ?: "p") {
41 | "p" -> ButtonMode.PRIMARY
42 | "s" -> ButtonMode.SECONDARY
43 | else -> ButtonMode.PRIMARY // Default value
44 | }
45 | isClickDisable = a.getBoolean(R.styleable.Button_mode, false)
46 | a.recycle()
47 | }
48 | text = getText().toString()
49 | attachTextChangeAnimator {
50 | fadeInMills = 300
51 | fadeOutMills = 300
52 | }
53 | setBackgroundDrawable(
54 | if (isClickDisable)
55 | this.getDisableDrawable()
56 | else this.getPrimaryDrawable()
57 | )
58 | setTextColor(
59 | LocalController.getColor(
60 | if (isClickDisable) R.color.button_text_deactive
61 | else R.color.button_text_active
62 | )
63 | )
64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
65 | typeface = LocalController.getFont(R.font.roboto_bold)
66 | }else{
67 | typeface = LocalController.getFont("roboto_bold.ttf")
68 | }
69 | textSize = 14f
70 | gravity = Gravity.CENTER
71 | }
72 |
73 | private fun getPrimaryDrawable(): GradientDrawable {
74 | return ThemeHelper.createRect(R.color.button_primary, 28)
75 | }
76 |
77 | private fun getDisableDrawable(): GradientDrawable {
78 | return ThemeHelper.createRect(R.color.button_disable, 28)
79 | }
80 |
81 | fun startProgress() {
82 | this.showProgress {
83 | progressColor = Color.WHITE
84 | gravity = DrawableButton.GRAVITY_CENTER
85 | }
86 | isEnabled = false
87 |
88 | }
89 |
90 | fun stopProgress() {
91 | hideProgress(text)
92 | isEnabled = true
93 | }
94 |
95 |
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/component/Icon.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.component
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.graphics.PorterDuff
6 | import android.net.Uri
7 | import android.util.AttributeSet
8 | import android.widget.TextView
9 | import androidx.appcompat.widget.AppCompatButton
10 | import androidx.appcompat.widget.AppCompatImageView
11 | import androidx.appcompat.widget.AppCompatTextView
12 | import androidx.lifecycle.LifecycleOwner
13 | import androidx.lifecycle.lifecycleScope
14 | import com.playsho.android.R
15 | import com.playsho.android.utils.LocalController
16 | import com.squareup.picasso.Picasso
17 | import kotlinx.coroutines.delay
18 | import kotlinx.coroutines.launch
19 | import java.io.File
20 |
21 | class Icon @JvmOverloads constructor(
22 | context: Context,
23 | attrs: AttributeSet? = null,
24 | defStyleAttr: Int = 0
25 | ) : AppCompatImageView (context, attrs, defStyleAttr) {
26 |
27 |
28 | init {
29 | if (!isInEditMode) {
30 | attrs?.let {
31 | val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.Icon, defStyleAttr, 0)
32 | val color = a.getColor(R.styleable.Icon_color, 0)
33 | if (color != 0) {
34 | setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
35 | }
36 | invalidate()
37 | a.recycle()
38 | }
39 |
40 | }
41 | }
42 |
43 | fun setColor(color: Int) {
44 | if (color != 0) {
45 | setColorFilter(LocalController.getColor(color), PorterDuff.Mode.SRC_ATOP)
46 | }
47 | invalidate()
48 | }
49 |
50 | fun load(uri: Uri) {
51 | Picasso.get().load(uri).into(this)
52 | }
53 |
54 | fun load(file: File) {
55 | Picasso.get().load(file).into(this)
56 | }
57 |
58 | fun load(path: String) {
59 | Picasso.get().load(path).into(this)
60 | }
61 |
62 | fun load(resourceId: Int) {
63 | Picasso.get().load(resourceId).into(this)
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/component/PhotoBoard.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.component
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatImageView
6 | import androidx.core.widget.ImageViewCompat
7 |
8 | class PhotoBoard : AppCompatImageView {
9 | constructor(context: Context) : super(context) {
10 | // Initialize your custom view
11 | }
12 |
13 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
14 | // Initialize your custom view with attributes
15 | }
16 |
17 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
18 | // Initialize your custom view with attributes and style
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/component/Sheet.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.component
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.TextView
6 | import androidx.appcompat.widget.AppCompatButton
7 | import androidx.appcompat.widget.AppCompatTextView
8 | import androidx.lifecycle.LifecycleOwner
9 | import androidx.lifecycle.lifecycleScope
10 | import kotlinx.coroutines.delay
11 | import kotlinx.coroutines.launch
12 |
13 | class Sheet @JvmOverloads constructor(
14 | context: Context,
15 | attrs: AttributeSet? = null,
16 | defStyleAttr: Int = 0
17 | ) : AppCompatTextView(context, attrs, defStyleAttr) {
18 |
19 | init {
20 |
21 | }
22 |
23 |
24 |
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/config/Conf.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.config
2 |
3 | object Conf {
4 |
5 | object HttpHeader {
6 | const val AUTHORIZATION = "Authorization"
7 | const val CONTENT_TYPE = "Content-Type"
8 | }
9 |
10 | object Path {
11 | const val V1_DEVICE_GENERATE = "/v1/api/device/generate"
12 | const val V1_DEVICE_NAME = "/v1/api/device/name"
13 | const val V1_DEVICE_KEYPAIR_REGENERATE = "/v1/api/device/keypair/regenerate"
14 | const val V1_ROOM_CREATE = "/v1/api/room"
15 | const val V1_ROOM_GET = "/v1/api/room/{tag}"
16 | const val V1_ROOM_ENTRANCE = "/v1/api/room/{tag}/entrance"
17 | const val V1_ROOM_LINK = "/v1/api/room/{tag}/link"
18 | }
19 |
20 | object Query {
21 | const val STREAM_LINK = "stream_link"
22 | const val OS = "os"
23 | const val APP_VER = "app_version"
24 | const val APP_VER_NAME = "app_version_name"
25 | const val OS_VER = "os_version"
26 | const val LAST_UPDATE_TIME = "last_update_at"
27 | const val FIRST_INSTALL_TIME = "first_install_at"
28 | const val MANUFACTURER = "manufacturer"
29 | const val MODEL = "device_model"
30 | const val BRAND = "brand"
31 | const val FCM = "fcm"
32 | const val SECRET = "secret"
33 | const val PUBLIC_KEY = "public_key"
34 | const val USER_NAME = "user_name"
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/data/Device.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.data
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Device(
7 |
8 | @SerializedName("tag")
9 | @Expose
10 | val tag: String? = "",
11 |
12 | @SerializedName("name")
13 | @Expose
14 | val name: String? = "",
15 |
16 | @SerializedName("color")
17 | @Expose
18 | var color: String? = "#f46c7d",
19 |
20 | @SerializedName("brand")
21 | @Expose
22 | val brand: String,
23 |
24 | @SerializedName("model")
25 | @Expose
26 | val model: String,
27 |
28 | @SerializedName("os")
29 | @Expose
30 | val os: String,
31 |
32 | @SerializedName("secret")
33 | @Expose
34 | val secret: String? = "",
35 |
36 | @SerializedName("manufacturer")
37 | @Expose
38 | val manufacturer: String,
39 |
40 | @SerializedName("os_version")
41 | @Expose
42 | val osVersion: String,
43 |
44 | @SerializedName("token")
45 | @Expose
46 | val token: String? = "",
47 |
48 | @SerializedName("user_name")
49 | @Expose
50 | val userName: String? = "unknown" ,
51 |
52 | @SerializedName("app_version_name")
53 | @Expose
54 | val appVersionName: String,
55 |
56 | @SerializedName("app_version")
57 | @Expose
58 | val appVersion: Int,
59 |
60 | @SerializedName("last_update_at")
61 | @Expose
62 | val lastUpdateAt: Long,
63 |
64 | @SerializedName("first_install_at")
65 | @Expose
66 | val firstInstallAt: Long,
67 |
68 | @SerializedName("public_key")
69 | @Expose
70 | var publicKey: String? = ""
71 | ) {
72 |
73 | override fun equals(other: Any?): Boolean {
74 | if (this === other) return true
75 | if (javaClass != other?.javaClass) return false
76 |
77 | other as Device
78 |
79 | return tag == other.tag
80 | // Check other properties similarly
81 | // return true if all properties are equal
82 |
83 | }
84 |
85 | override fun hashCode(): Int {
86 | return tag.hashCode()
87 | }
88 |
89 | override fun toString(): String {
90 | return "Device(tag='$tag', name='$name', brand='$brand', os='$os', secret='$secret', manufacturer='$manufacturer', osVersion='$osVersion', token='$token', userName='$userName', appVersionName='$appVersionName', appVersion=$appVersion, lastUpdateAt=$lastUpdateAt, firstInstallAt=$firstInstallAt)"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/data/Message.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.data
2 |
3 | data class Message(var sender: Device, var room: String, var payload: String, var action: String, var type: String, var tag: String, var message: String, var data: String, var created_at: Long) {
4 | override fun toString(): String {
5 | return "Message(sender=$sender, room='$room', tag='$tag', message='$message', created_at=$created_at)"
6 | }
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/data/Room.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.data
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Room(
7 | @SerializedName("tag")
8 | @Expose
9 | val tag: String = "",
10 |
11 | @SerializedName("stream_link")
12 | @Expose
13 | var streamLink: String? = "",
14 |
15 | @SerializedName("status")
16 | @Expose
17 | val status: String? = "",
18 |
19 | @SerializedName("room_key")
20 | @Expose
21 | var roomKey: String? = "",
22 |
23 | ) {
24 |
25 | override fun equals(other: Any?): Boolean {
26 | if (this === other) return true
27 | if (javaClass != other?.javaClass) return false
28 |
29 | other as Room
30 |
31 | return tag == other.tag
32 | // Check other properties similarly
33 | // return true if all properties are equal
34 |
35 | }
36 |
37 | override fun hashCode(): Int {
38 | return tag.hashCode()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/db/SessionStorage.java:
--------------------------------------------------------------------------------
1 | package com.playsho.android.db;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import com.playsho.android.base.ApplicationLoader;
7 | import com.playsho.android.utils.Validator;
8 |
9 | /**
10 | * A class for managing session storage using SharedPreferences.
11 | */
12 | public class SessionStorage {
13 | private final SharedPreferences pref;
14 | private final SharedPreferences.Editor editor;
15 |
16 | // Name of the shared preference file
17 | private final String SHARED_PREFERENCE_NAME = "main_sp";
18 |
19 | /**
20 | * Constructs a SessionStorage instance and initializes SharedPreferences and its editor.
21 | */
22 | public SessionStorage() {
23 | this.pref = ApplicationLoader.context.getSharedPreferences(
24 | SHARED_PREFERENCE_NAME,
25 | Context.MODE_PRIVATE
26 | );
27 | editor = pref.edit();
28 | editor.apply();
29 | }
30 |
31 | /**
32 | * Clears all entries in the SharedPreferences.
33 | */
34 | public void clearAll(){
35 | pref.edit().clear().apply();
36 | }
37 |
38 | /**
39 | * Gets a String value from the SharedPreferences with the specified key.
40 | *
41 | * @param key The key for the String value.
42 | * @return The String value associated with the key, or {@code null} if not found.
43 | */
44 | public String getString(String key) {
45 | return pref.getString(key, null);
46 | }
47 |
48 |
49 | /**
50 | * Gets a String value from the SharedPreferences with the specified key, providing a default value if not found.
51 | *
52 | * @param key The key for the String value.
53 | * @param defValue The default value to return if the key is not found.
54 | * @return The String value associated with the key, or the default value if not found.
55 | */
56 | public String getString(String key , String defValue) {
57 | return pref.getString(key, defValue);
58 | }
59 |
60 | /**
61 | * Sets a String value in the SharedPreferences with the specified key.
62 | *
63 | * @param key The key for the String value.
64 | * @param value The String value to set.
65 | */
66 | public void setString(String key, String value) {
67 | editor.putString(key, value);
68 | editor.apply();
69 | editor.commit();
70 | }
71 |
72 | /**
73 | * Gets an integer value from the SharedPreferences with the specified key.
74 | *
75 | * @param key The key for the integer value.
76 | * @return The integer value associated with the key, or 0 if not found.
77 | */
78 | public int getInteger(String key) {
79 | return pref.getInt(key, 0);
80 | }
81 |
82 | /**
83 | * Gets an integer value from the SharedPreferences with the specified key, providing a default value if not found.
84 | *
85 | * @param key The key for the integer value.
86 | * @param defValue The default value to return if the key is not found.
87 | * @return The integer value associated with the key, or the default value if not found.
88 | */
89 | public int getInteger(String key , int defValue) {
90 | return pref.getInt(key, defValue);
91 | }
92 |
93 | /**
94 | * Sets an integer value in the SharedPreferences with the specified key.
95 | *
96 | * @param key The key for the integer value.
97 | * @param value The integer value to set.
98 | */
99 | public void setInteger(String key, int value) {
100 | editor.putInt(key, value);
101 | editor.apply();
102 | editor.commit();
103 | }
104 |
105 |
106 | /**
107 | * Deserializes a JSON string stored in SharedPreferences into an object of the specified class.
108 | *
109 | * @param key The key for the JSON string.
110 | * @param clazz The class type to deserialize the JSON into.
111 | * @param The type of the class.
112 | * @return An object of the specified class, or a default object if the JSON is not found.
113 | */
114 | /* public T deserialize(String key, Class clazz) {
115 | String json = this.getString(key);
116 | if (Validator.isNullOrEmpty(json)) {
117 | json = "{}";
118 | }
119 | return ApplicationLoader.getGson().fromJson(json, clazz);
120 | }
121 | */
122 | /**
123 | * Inserts a JSON-serializable object into SharedPreferences with the specified key.
124 | *
125 | * @param key The key for storing the JSON string.
126 | * @param o The object to be serialized and stored.
127 | */
128 | // public void insertJson(String key, Object o) {
129 | // this.editor.putString(key,ApplicationLoader.getGson().toJson(o));
130 | // editor.apply();
131 | // editor.commit();
132 | // }
133 |
134 |
135 | /**
136 | * Gets a boolean value from the SharedPreferences with the specified key.
137 | *
138 | * @param key The key for the boolean value.
139 | * @return The boolean value associated with the key, or {@code false} if not found.
140 | */
141 | public boolean getBoolean(String key) {
142 | return pref.getBoolean(key, false);
143 | }
144 |
145 | /**
146 | * Gets a boolean value from the SharedPreferences with the specified key, providing a default value if not found.
147 | *
148 | * @param key The key for the boolean value.
149 | * @param defValue The default value to return if the key is not found.
150 | * @return The boolean value associated with the key, or the default value if not found.
151 | */
152 | public boolean getBoolean(String key, boolean defValue) {
153 | return pref.getBoolean(key, defValue);
154 | }
155 |
156 | /**
157 | * Sets a boolean value in the SharedPreferences with the specified key.
158 | *
159 | * @param key The key for the boolean value.
160 | * @param value The boolean value to set.
161 | */
162 | public void setBoolean(String key, boolean value) {
163 | editor.putBoolean(key, value);
164 | editor.apply();
165 | editor.commit();
166 | }
167 |
168 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/APIService.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.playsho.android.config.Conf
4 | import okhttp3.RequestBody
5 | import retrofit2.Call
6 | import retrofit2.http.*
7 |
8 | interface APIService {
9 |
10 | @POST(Conf.Path.V1_DEVICE_GENERATE)
11 | fun generateDevice(@Body body: RequestBody): Call
12 |
13 | @POST(Conf.Path.V1_ROOM_CREATE)
14 | fun createRoom(): Call
15 |
16 | @POST(Conf.Path.V1_DEVICE_NAME)
17 | fun updateName(@Body body: RequestBody): Call
18 |
19 | @POST(Conf.Path.V1_DEVICE_KEYPAIR_REGENERATE)
20 | fun regenerateDeviceKeypair(@Body body: RequestBody): Call
21 |
22 | @GET(Conf.Path.V1_ROOM_GET)
23 | fun getRoom(
24 | @Path("tag") tag: String
25 | ): Call
26 |
27 | @GET(Conf.Path.V1_ROOM_ENTRANCE)
28 | fun checkEntrance(
29 | @Path("tag") tag: String
30 | ): Call
31 |
32 | @POST(Conf.Path.V1_ROOM_LINK)
33 | fun addLink(
34 | @Path("tag") tag: String,
35 | @Body body: RequestBody
36 | ): Call
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/Agent.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.playsho.android.config.Conf
4 | import com.playsho.android.utils.DeviceUtils
5 | import okhttp3.FormBody
6 | import okhttp3.RequestBody
7 | import retrofit2.Call
8 | import java.security.PublicKey
9 |
10 |
11 | object Agent {
12 |
13 | object Device {
14 |
15 | fun generate(privateKey: String): Call {
16 | val device = DeviceUtils.createDevice();
17 | val builder = FormBody.Builder().apply {
18 | device.secret?.let { add(Conf.Query.SECRET, it) }
19 | add(Conf.Query.PUBLIC_KEY, privateKey)
20 | add(Conf.Query.FCM, "not_set")
21 | add(Conf.Query.BRAND, device.brand)
22 | add(Conf.Query.MODEL, device.model)
23 | add(Conf.Query.MANUFACTURER, device.manufacturer)
24 | add(Conf.Query.FIRST_INSTALL_TIME, device.firstInstallAt.toString())
25 | add(Conf.Query.LAST_UPDATE_TIME, device.lastUpdateAt.toString())
26 | add(Conf.Query.OS_VER, device.osVersion)
27 | add(Conf.Query.APP_VER_NAME, device.appVersionName)
28 | add(Conf.Query.APP_VER, device.appVersion.toString())
29 | add(Conf.Query.OS, DeviceUtils.OS)
30 | }
31 | val body: RequestBody = builder.build()
32 | return RetrofitClient.getNetworkConfiguration().generateDevice(body)
33 | }
34 |
35 | fun regenerateKeypair(publicKey: String): Call {
36 | val builder = FormBody.Builder().apply {
37 | add(Conf.Query.PUBLIC_KEY, publicKey)
38 | }
39 | val body: RequestBody = builder.build()
40 | return RetrofitClient.getNetworkConfiguration().regenerateDeviceKeypair(body)
41 | }
42 |
43 | fun updateName(name: String): Call {
44 | val builder = FormBody.Builder().apply {
45 | add(Conf.Query.USER_NAME, name)
46 | }
47 | val body: RequestBody = builder.build()
48 | return RetrofitClient.getNetworkConfiguration().updateName(body)
49 | }
50 | }
51 |
52 | object Room {
53 | fun create(): Call {
54 | return RetrofitClient.getNetworkConfiguration().createRoom()
55 | }
56 |
57 | fun get(roomTag: String): Call {
58 | return RetrofitClient.getNetworkConfiguration().getRoom(roomTag);
59 | }
60 |
61 | fun checkEntrance(roomTag: String): Call {
62 | return RetrofitClient.getNetworkConfiguration().checkEntrance(roomTag);
63 | }
64 |
65 | fun addLink(roomTag: String , link:String): Call {
66 | val builder = FormBody.Builder().apply {
67 | add(Conf.Query.STREAM_LINK, link)
68 | }
69 | val body: RequestBody = builder.build()
70 | return RetrofitClient.getNetworkConfiguration().addLink(roomTag,body);
71 | }
72 | }
73 |
74 |
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/Errors.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Errors(
7 |
8 | @SerializedName("property")
9 | @Expose
10 | val property: String,
11 |
12 | @SerializedName("message")
13 | @Expose
14 | val message: String,
15 |
16 | @SerializedName("value")
17 | @Expose
18 | val value: Any,
19 | )
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/Meta.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Meta(
7 |
8 | @SerializedName("page")
9 | @Expose
10 | val page: Int,
11 |
12 | @SerializedName("limit")
13 | @Expose
14 | val limit: Int,
15 |
16 | @SerializedName("totalPage")
17 | @Expose
18 | val totalPage: Int,
19 |
20 | @SerializedName("count")
21 | @Expose
22 | val count: Int
23 | )
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/Response.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Response(
7 |
8 | @SerializedName("message")
9 | @Expose
10 | val message: String,
11 |
12 |
13 | @SerializedName("code")
14 | @Expose
15 | val code: Int,
16 |
17 | @SerializedName("result")
18 | @Expose
19 | val result: Result,
20 |
21 | @SerializedName("errors")
22 | @Expose
23 | val errors: List,
24 |
25 | @SerializedName("meta")
26 | @Expose
27 | val meta: Meta
28 | ) {
29 |
30 | override fun toString(): String {
31 | return "Response{" +
32 | "message='$message', " +
33 | "code=$code, " +
34 | "result=$result, " +
35 | "errors=$errors, " +
36 | "meta=$meta" +
37 | '}'
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/Result.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import com.google.gson.annotations.Expose
4 | import com.google.gson.annotations.SerializedName
5 | import com.playsho.android.data.Device
6 | import com.playsho.android.data.Room
7 |
8 | data class Result(
9 |
10 |
11 |
12 | @SerializedName("device")
13 | @Expose
14 | val device: Device,
15 |
16 | @SerializedName("room")
17 | @Expose
18 | val room: Room,
19 |
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/RetrofitClient.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 | import com.google.gson.GsonBuilder
3 | import com.playsho.android.config.Conf
4 | import com.playsho.android.utils.DebugUtils
5 | import com.playsho.android.utils.Validator
6 | import com.playsho.android.utils.accountmanager.AccountInstance
7 | import okhttp3.ConnectionSpec
8 | import okhttp3.OkHttpClient
9 | import okhttp3.Request
10 | import okhttp3.logging.HttpLoggingInterceptor
11 | import retrofit2.Retrofit
12 | import retrofit2.converter.gson.GsonConverterFactory
13 | import retrofit2.converter.scalars.ScalarsConverterFactory
14 | import java.util.concurrent.TimeUnit
15 |
16 | object RetrofitClient {
17 |
18 | private const val REQUEST_READ_TIME_OUT = 15L
19 | private const val REQUEST_CONNECT_TIME_OUT = 15L
20 | // private const val BASE_URL_TEST = "http://192.168.100.110:3000/"
21 | private const val BASE_URL_TEST = "https://5d44-185-107-80-116.ngrok-free.app"
22 | // private const val SOCKET_URL_TEST = "http://192.168.100.110:7777"
23 | private const val SOCKET_URL_TEST = "https://f3e7-185-107-80-116.ngrok-free.app"
24 | private const val BASE_URL = "https://d.digilog.pro/"
25 |
26 | private val CONNECTION_SPECS = listOf(
27 | ConnectionSpec.COMPATIBLE_TLS,
28 | ConnectionSpec.RESTRICTED_TLS,
29 | ConnectionSpec.CLEARTEXT,
30 | ConnectionSpec.MODERN_TLS
31 | )
32 |
33 | private var instance: Retrofit? = null
34 |
35 | private fun getInstance(): Retrofit {
36 | return instance ?: synchronized(this) {
37 | instance ?: buildRetrofit().also { instance = it }
38 | }
39 | }
40 |
41 | private fun buildRetrofit(): Retrofit {
42 | return Retrofit.Builder()
43 | .baseUrl(getBaseUrl())
44 | .addConverterFactory(ScalarsConverterFactory.create())
45 | .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
46 | .client(getHttpClient())
47 | .build()
48 | }
49 |
50 | fun getBaseUrl(): String {
51 | return if (DebugUtils.isDebuggable()) BASE_URL_TEST else BASE_URL
52 | }
53 |
54 | fun getSocketBaseUrl(): String {
55 | return SOCKET_URL_TEST
56 | }
57 |
58 | private fun getHttpClient(): OkHttpClient {
59 | val client = OkHttpClient.Builder()
60 | .addInterceptor { chain ->
61 | val requestBuilder: Request.Builder = chain.request().newBuilder()
62 | requestBuilder.addHeader(Conf.HttpHeader.CONTENT_TYPE, "application/json")
63 | if (!Validator.isNull(AccountInstance.getCurrentAccount())) {
64 | requestBuilder.addHeader(
65 | Conf.HttpHeader.AUTHORIZATION,
66 | "Bearer ".plus(AccountInstance.getCurrentAccount()
67 | ?.let { AccountInstance.getAuthToken(it, "Bearer") })
68 | )
69 | }
70 | chain.proceed(requestBuilder.build())
71 | }
72 | .connectionSpecs(CONNECTION_SPECS)
73 | .readTimeout(REQUEST_READ_TIME_OUT, TimeUnit.SECONDS)
74 | .connectTimeout(REQUEST_CONNECT_TIME_OUT, TimeUnit.SECONDS)
75 | if (DebugUtils.isDebuggable()) {
76 | val logging = HttpLoggingInterceptor()
77 | logging.setLevel(HttpLoggingInterceptor.Level.BODY)
78 | client.addInterceptor(logging)
79 | }
80 | return client.build()
81 | }
82 |
83 | fun getNetworkConfiguration(): APIService {
84 | return getInstance().create(APIService::class.java)
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/network/SocketManager.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.network
2 |
3 | import android.util.Log
4 | import com.playsho.android.utils.accountmanager.AccountInstance
5 | import io.socket.client.IO
6 | import io.socket.client.Socket
7 | import org.json.JSONException
8 | import org.json.JSONObject
9 |
10 | object SocketManager {
11 | private const val TAG = "SocketManager"
12 | private var socket: Socket? = null
13 |
14 | object EVENTS {
15 | const val SEND_MESSAGE = "room_msg"
16 | const val NEW_MESSAGE = "new_message"
17 | const val JOINED = "joined"
18 | const val LEFT = "left"
19 | const val NEW_LINK = "new_link"
20 | const val TRADE = "trade"
21 | const val PAUSE = "pause"
22 | const val PLAYER_STATE = "player_state"
23 | }
24 |
25 | @Synchronized
26 | fun initialize(): SocketManager {
27 | if (socket == null) {
28 | synchronized(SocketManager::class.java) {
29 | if (socket == null) {
30 |
31 | val options = IO.Options().apply {
32 | // Set transports to WebSocket only
33 | forceNew = true
34 | query = "token=${AccountInstance.getAuthToken("Bearer")}"
35 |
36 | }
37 | // Create a new socket instance
38 | socket = IO.socket(RetrofitClient.getSocketBaseUrl(), options)
39 |
40 | socket!!.on(Socket.EVENT_CONNECT) { args ->
41 | Log.e(TAG, "EVENT_CONNECT:")
42 | for (element in args) {
43 | println(element)
44 | }
45 | }
46 |
47 | socket!!.on(Socket.EVENT_CONNECT_ERROR) { args ->
48 | Log.e(TAG, "EVENT_CONNECT_ERROR: $args")
49 | for (element in args) {
50 | println(element)
51 | }
52 | }
53 |
54 | socket!!.on(Socket.EVENT_DISCONNECT) { args ->
55 | Log.e(TAG, "EVENT_DISCONNECT: $args")
56 | for (element in args) {
57 | println(element)
58 | }
59 | }
60 |
61 | socket!!.on(Socket.EVENT_DISCONNECT) { args ->
62 | Log.e(TAG, "EVENT_DISCONNECT: $args")
63 | for (element in args) {
64 | println(element)
65 | }
66 | }
67 | }
68 | }
69 | }
70 | return this;
71 | }
72 |
73 | fun joinRoom(room: String) {
74 | val json = JSONObject()
75 | try {
76 | json.put("room", room)
77 | } catch (e: JSONException) {
78 | e.printStackTrace()
79 | }
80 | socket?.emit("join", json)
81 | }
82 |
83 | fun leaveRoom(room: String) {
84 | val json = JSONObject()
85 | try {
86 | json.put("room", room)
87 | } catch (e: JSONException) {
88 | e.printStackTrace()
89 | }
90 | socket?.emit("leave", json)
91 | }
92 |
93 | @Synchronized
94 | fun establish() {
95 | socket?.connect()
96 | }
97 |
98 | @Synchronized
99 | fun close() {
100 | socket?.disconnect()
101 | socket = null
102 | }
103 |
104 | fun on(eventName: String, callback: (Array) -> Unit) {
105 | socket?.on(eventName, callback)
106 | }
107 |
108 | @Synchronized
109 | fun sendMessage(room: String, message: String) {
110 | val json = JSONObject()
111 | try {
112 | json.put("room", room)
113 | json.put("message", message)
114 | } catch (e: JSONException) {
115 | e.printStackTrace()
116 | }
117 | socket?.emit(EVENTS.SEND_MESSAGE, json)
118 | }
119 |
120 |
121 | @Synchronized
122 | fun sendPlayerState(room: String, state: String, data: String = "") {
123 | val json = JSONObject()
124 | try {
125 | json.put("room", room)
126 | json.put("message", state)
127 | if (data.isNotEmpty()) json.put("data", data)
128 | } catch (e: JSONException) {
129 | e.printStackTrace()
130 | }
131 | socket?.emit(EVENTS.PLAYER_STATE, json)
132 | }
133 |
134 | @Synchronized
135 | fun trade(tag: String) {
136 | val json = JSONObject()
137 | try {
138 | val senderJson = JSONObject()
139 | senderJson.put("tag", AccountInstance.getUserData("tag"))
140 | senderJson.put("public_key", AccountInstance.getAuthToken("public_key"))
141 | val receiverJson = JSONObject()
142 | receiverJson.put("tag", tag)
143 | json.put("sender", senderJson)
144 | json.put("receiver", receiverJson)
145 | } catch (e: JSONException) {
146 | e.printStackTrace()
147 | }
148 | socket?.emit(EVENTS.TRADE, json)
149 | }
150 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/ui/SettingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.ui
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.view.View
6 | import com.google.android.material.snackbar.Snackbar
7 | import com.playsho.android.R
8 | import com.playsho.android.base.BaseActivity
9 | import com.playsho.android.base.BaseBottomSheet
10 | import com.playsho.android.databinding.ActivitySettingBinding
11 | import com.playsho.android.network.Agent
12 | import com.playsho.android.network.Response
13 | import com.playsho.android.ui.bottomsheet.ChangeNameBottomSheet
14 | import com.playsho.android.utils.KeyStoreHelper
15 | import com.playsho.android.utils.LocalController
16 | import com.playsho.android.utils.RSAHelper
17 | import com.playsho.android.utils.accountmanager.AccountInstance
18 | import retrofit2.Call
19 | import retrofit2.Callback
20 |
21 | class SettingActivity : BaseActivity() {
22 |
23 |
24 | override fun getLayoutResourceId(): Int {
25 | return R.layout.activity_setting
26 | }
27 |
28 | override fun onBackPress() {
29 | TODO("Not yet implemented")
30 | }
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setStatusBarColor(R.color.black_background, true)
35 | binding.icBack.setOnClickListener {
36 | finish()
37 | }
38 | binding.containerGenerateKeys.setOnClickListener {
39 | binding.progress.visibility = View.VISIBLE
40 | KeyStoreHelper.getInstance().deleteEntry(KeyStoreHelper.KeyAllies.RSA_KEYS)
41 |
42 | val keys = RSAHelper.generateKeyPair()
43 | // Create a certificate chain containing the public key
44 |
45 | Agent.Device.regenerateKeypair(RSAHelper.printPublicKey(keys))
46 | .enqueue(object : Callback {
47 |
48 | override fun onFailure(call: Call, t: Throwable) {
49 | binding.progress.visibility = View.GONE
50 | Snackbar.make(
51 | binding.root,
52 | LocalController.getString(R.string.rsa_key_pair_successfully_regenerated),
53 | Snackbar.LENGTH_LONG
54 | ).show()
55 | }
56 |
57 | override fun onResponse(
58 | call: Call,
59 | response: retrofit2.Response
60 | ) {
61 | binding.progress.visibility = View.GONE
62 | Snackbar.make(
63 | binding.root,
64 | response.body()?.message
65 | ?: LocalController.getString(R.string.rsa_key_pair_successfully_regenerated),
66 | Snackbar.LENGTH_LONG
67 | ).show()
68 | }
69 | })
70 |
71 | }
72 |
73 | binding.containerChangeName.setOnClickListener {
74 | val bottomSheet = ChangeNameBottomSheet()
75 | bottomSheet.setOnResult(callback = object : BaseBottomSheet.BottomSheetResultCallback {
76 | override fun onBottomSheetProcessSuccess(data: String) {
77 | loadData()
78 | Snackbar.make(
79 | binding.root,
80 | LocalController.getString(R.string.name_successfully_updated),
81 | Snackbar.LENGTH_LONG
82 | ).show()
83 | }
84 |
85 | override fun onBottomSheetProcessFail(data: String) {
86 | Snackbar.make(
87 | binding.root,
88 | data,
89 | Snackbar.LENGTH_LONG
90 | ).show()
91 | }
92 | })
93 | bottomSheet.show(supportFragmentManager, "name")
94 | }
95 | loadData()
96 | }
97 |
98 | @SuppressLint("SetTextI18n")
99 | private fun loadData() {
100 | binding.txtTag.text = "Tag:${AccountInstance.getUserData("tag")}"
101 | binding.txtUserName.text = AccountInstance.getUserData("user_name")
102 | }
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/ui/bottomsheet/ChangeNameBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.ui.bottomsheet
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import android.view.inputmethod.EditorInfo
6 | import com.google.android.material.snackbar.Snackbar
7 | import com.google.gson.Gson
8 | import com.playsho.android.R
9 | import com.playsho.android.base.BaseBottomSheet
10 | import com.playsho.android.databinding.BottomSheetChangeNameBinding
11 | import com.playsho.android.network.Agent
12 | import com.playsho.android.network.Response
13 | import com.playsho.android.utils.SystemUtilities
14 | import com.playsho.android.utils.ThemeHelper
15 | import com.playsho.android.utils.accountmanager.AccountInstance
16 | import retrofit2.Call
17 | import retrofit2.Callback
18 |
19 | class ChangeNameBottomSheet : BaseBottomSheet() {
20 |
21 | lateinit var callback: BottomSheetResultCallback
22 |
23 | override fun getLayoutResourceId(): Int {
24 | return R.layout.bottom_sheet_change_name
25 | }
26 |
27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
28 | super.onViewCreated(view, savedInstanceState)
29 | binding.input.setText(AccountInstance.getUserData("user_name"))
30 | binding.input.requestFocus() // Set focus to EditText
31 | binding.input.selectAll() // Select all text in EditText
32 | binding.input.background = ThemeHelper.createRect(
33 | R.color.neutral_100,
34 | 45,
35 | )
36 | binding.btn.setOnClickListener{
37 | requestChangeName()
38 | }
39 | binding.input.setOnEditorActionListener { _, actionId, _ ->
40 | if (actionId == EditorInfo.IME_ACTION_DONE) {
41 | SystemUtilities.hideKeyboard(binding.input)
42 | requestChangeName()
43 | // Handle the "Go" action here
44 | // For example, perform some action or submit the form
45 | return@setOnEditorActionListener true // Return true to indicate that the action was handled
46 | }
47 | return@setOnEditorActionListener false // Return false if the action was not handled
48 | }
49 | }
50 |
51 | fun setOnResult(callback: BottomSheetResultCallback){
52 | this.callback = callback
53 | }
54 |
55 | private fun requestChangeName(){
56 | SystemUtilities.hideKeyboard(binding.input)
57 | binding.btn.startProgress()
58 | Agent.Device.updateName(binding.input.text.toString()).enqueue(object :
59 | Callback {
60 |
61 | override fun onFailure(call: Call, t: Throwable) {
62 | binding.input.requestFocus()
63 | binding.input.selectAll()
64 | SystemUtilities.showKeyboard(binding.input)
65 | binding.btn.stopProgress()
66 | callback.onBottomSheetProcessFail("")
67 | }
68 |
69 | override fun onResponse(
70 | call: Call,
71 | response: retrofit2.Response
72 | ) {
73 | binding.btn.stopProgress()
74 | if (response.isSuccessful){
75 | AccountInstance.updateAccountUserData("user_name" , binding.input.text.toString())
76 | binding.btn.stopProgress()
77 | callback.onBottomSheetProcessSuccess(binding.input.text.toString())
78 | dismiss()
79 | }else{
80 | val errorResponse = response.errorBody()?.string()?.let {
81 | Gson().fromJson(it, Response::class.java)
82 | }
83 | Snackbar.make(
84 | dialog?.window?.decorView ?:requireActivity().findViewById(android.R.id.content),
85 | errorResponse?.errors?.getOrNull(0)?.message ?: "Error",
86 | Snackbar.LENGTH_LONG
87 | ).show()
88 | }
89 |
90 | }
91 | })
92 | }
93 |
94 | override fun initView() {
95 | TODO("Not yet implemented")
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/ui/bottomsheet/SendMessageBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.ui.bottomsheet
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import android.view.inputmethod.EditorInfo
6 | import com.playsho.android.R
7 | import com.playsho.android.base.BaseBottomSheet
8 | import com.playsho.android.databinding.BottomSheetSendMessageBinding
9 | import com.playsho.android.utils.SystemUtilities
10 | import com.playsho.android.utils.ThemeHelper
11 |
12 | class SendMessageBottomSheet() : BaseBottomSheet() {
13 | override fun getLayoutResourceId(): Int {
14 | return R.layout.bottom_sheet_send_message
15 | }
16 | lateinit var callback: BottomSheetResultCallback
17 |
18 | var titleArray = arrayOf(
19 | "Spill the Popcorn",
20 | "Meme the Moment",
21 | "Live Commentary (Shhh!)",
22 | "Battle Royale of Thoughts",
23 | "Squad Chat: Activate!",
24 | "Mind Meld with Your Crew",
25 | "The Aftershow Starts Now",
26 | "Is This Scene Even Real? Discuss!",
27 | "Who Needs Subtitles? Talk it Out!",
28 | "Spoiler Alert (Just Kidding)",
29 | "The Chat Awakens",
30 | "Let's Get This Party Started (In the Chat)",
31 | "Don't Panic (But Talk in the Chat)",
32 | "Let's Dish About the Movie",
33 | "Spill the Tea (and Snack Commentary)",
34 | "Shhh... We're Chatting About the Movie"
35 | )
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 |
39 | binding.input.background = ThemeHelper.createRect(
40 | R.color.neutral_100,
41 | 45,
42 | )
43 |
44 | binding.icSend.setOnClickListener {
45 | if (binding.input.text.toString().trim().isNotEmpty()) {
46 | this.callback.onBottomSheetProcessSuccess(binding.input.text.toString().trim())
47 | }
48 | dismiss()
49 | }
50 | binding.input.setOnEditorActionListener { _, actionId, _ ->
51 | if (actionId == EditorInfo.IME_ACTION_DONE) {
52 | SystemUtilities.hideKeyboard(binding.input)
53 | if (binding.input.text.toString().trim().isNotEmpty()) {
54 | this.callback.onBottomSheetProcessSuccess(binding.input.text.toString().trim())
55 | }
56 | dismiss()
57 | return@setOnEditorActionListener true // Return true to indicate that the action was handled
58 | }
59 | return@setOnEditorActionListener false // Return false if the action was not handled
60 | }
61 | }
62 |
63 | fun setOnResult(callback: BottomSheetResultCallback){
64 | this.callback = callback
65 | }
66 |
67 | override fun initView() {
68 | TODO("Not yet implemented")
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/ui/popup/CinemaSettingPopup.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.ui.popup
2 |
3 | import android.content.Context
4 | import com.playsho.android.R
5 | import com.playsho.android.base.BasePopup
6 | import com.playsho.android.databinding.PopupCinemaSettingBinding
7 |
8 | class CinemaSettingPopup(context: Context) : BasePopup(context) {
9 | override fun getLayoutResourceId(): Int {
10 | return R.layout.popup_cinema_setting
11 | }
12 |
13 | override fun onViewInitialized() {
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/AnimationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.content.ClipDescription
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import android.view.View
7 | import android.view.animation.AlphaAnimation
8 | import android.view.animation.Animation
9 | import android.view.animation.TranslateAnimation
10 | import com.playsho.android.base.ApplicationLoader
11 |
12 | object AnimationHelper {
13 |
14 | object Duration {
15 | const val DISAPPEAR_MESSAGE = 5000L
16 | const val SLIDE_IN_FROM_BOTTOM = 400L
17 | }
18 |
19 | fun slideInFromBottom(view: View, duration: Long) {
20 | val animation = TranslateAnimation(
21 | Animation.RELATIVE_TO_PARENT, 0.0f,
22 | Animation.RELATIVE_TO_PARENT, 0.0f,
23 | Animation.RELATIVE_TO_PARENT, 1.0f,
24 | Animation.RELATIVE_TO_PARENT, 0.0f
25 | )
26 | animation.duration = duration
27 | view.startAnimation(animation)
28 | }
29 |
30 | fun fadeIn(view: View, duration: Long) {
31 | val animation = AlphaAnimation(0.0f, 1.0f)
32 | animation.duration = duration
33 | view.startAnimation(animation)
34 | }
35 |
36 | fun fadeOut(view: View, duration: Long) {
37 | val animation = AlphaAnimation(1.0f, 0.0f)
38 | animation.duration = duration
39 | view.startAnimation(animation)
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/ClipboardHandler.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.content.ClipDescription
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import com.playsho.android.base.ApplicationLoader
7 |
8 | object ClipboardHandler {
9 | private var clipBoardInstance : ClipboardManager = ApplicationLoader.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
10 |
11 | fun hasTextPlainData():Boolean {
12 | return clipBoardInstance.hasPrimaryClip() &&
13 | clipBoardInstance.primaryClipDescription?.hasMimeType(
14 | ClipDescription.MIMETYPE_TEXT_PLAIN
15 | ) == true
16 | }
17 |
18 | fun getIndex(index:Int):String{
19 | return clipBoardInstance.primaryClip?.getItemAt(index)?.text.toString()
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/Crypto.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.util.Base64
4 | import java.security.SecureRandom
5 | import javax.crypto.Cipher
6 | import javax.crypto.spec.IvParameterSpec
7 | import javax.crypto.spec.SecretKeySpec
8 |
9 | object Crypto {
10 |
11 | private const val AES_ALGORITHM = "AES"
12 | private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
13 | private const val AES_IV_SIZE = 16 // 16 bytes for AES
14 |
15 | private fun generateIV(): ByteArray {
16 | val iv = ByteArray(AES_IV_SIZE) // 16 bytes for AES
17 | SecureRandom().nextBytes(iv)
18 | return iv
19 | }
20 |
21 | private fun hexStringToByteArray(hexString: String): ByteArray {
22 | val len = hexString.length
23 | require(len % 2 == 0) { "Hex string length must be even" }
24 | val data = ByteArray(len / 2)
25 | var i = 0
26 | while (i < len) {
27 | data[i / 2] = ((Character.digit(hexString[i], 16) shl 4) + Character.digit(
28 | hexString[i + 1],
29 | 16
30 | )).toByte()
31 | i += 2
32 | }
33 | return data
34 | }
35 |
36 | fun encryptAES(plainText: String, key: String): String {
37 | val cipher = Cipher.getInstance(TRANSFORMATION)
38 | val secretKeySpec = SecretKeySpec(hexStringToByteArray(key), AES_ALGORITHM)
39 | val iv: ByteArray = generateIV()
40 | val ivParameterSpec = IvParameterSpec(iv)
41 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
42 | val cipherText = cipher.doFinal(plainText.toByteArray())
43 | return Base64.encodeToString(iv, Base64.DEFAULT).plus(":").plus(
44 | Base64.encodeToString(
45 | cipherText,
46 | Base64.DEFAULT
47 | )
48 | )
49 | }
50 |
51 | fun decryptAES(cipherText: String, key: String): String {
52 | val message = cipherText.split(":")
53 | val cipher = Cipher.getInstance(TRANSFORMATION)
54 | val secretKeySpec = SecretKeySpec(hexStringToByteArray(key), AES_ALGORITHM)
55 | val ivParameterSpec = IvParameterSpec(Base64.decode(message[0], Base64.DEFAULT))
56 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
57 | val plainText = cipher.doFinal(Base64.decode(message[1], Base64.DEFAULT))
58 | return String(plainText)
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/Debouncer.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | /**
7 | * A utility class for debouncing operations in Android applications.
8 | *
9 | * Debouncing is a technique used to limit the frequency of execution of a particular action or task,
10 | * ensuring that it only happens after a specified delay without any additional triggers.
11 | *
12 | * This class allows you to debounce a Runnable, preventing it from running until a certain amount
13 | * of time has passed without further triggers. It's particularly useful in scenarios where you want
14 | * to delay or prevent rapid, repetitive actions such as button clicks or user input.
15 | */
16 | class Debouncer(private val delayMillis: Long) {
17 | private val handler = Handler(Looper.getMainLooper())
18 | private var runnable: Runnable? = null
19 |
20 | /**
21 | * Debounces a Runnable, ensuring it is executed after the specified delay, and canceling
22 | * any previously scheduled executions.
23 | *
24 | * @param runnable The Runnable to be debounced.
25 | */
26 | fun debounce(runnable: Runnable) {
27 | // Cancel the previous runnable if it exists
28 | cancel()
29 |
30 | this.runnable = runnable
31 | handler.postDelayed(runnable, delayMillis)
32 | }
33 |
34 | /**
35 | * Cancels any pending execution of the debounced Runnable.
36 | */
37 | fun cancel() {
38 | runnable?.let {
39 | handler.removeCallbacks(it)
40 | runnable = null
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/DebugUtils.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.content.pm.PackageManager
4 | import com.playsho.android.base.ApplicationLoader
5 |
6 | object DebugUtils {
7 |
8 | fun isDebuggable(): Boolean {
9 | return try {
10 | val pm = ApplicationLoader.context.packageManager
11 | val appInfo = pm.getApplicationInfo(
12 | ApplicationLoader.context.packageName,
13 | 0
14 | )
15 | (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
16 | } catch (e: PackageManager.NameNotFoundException) {
17 | // Handle exception if package is not found
18 | false
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/DimensionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 | import android.content.Context
3 | import android.content.res.Configuration
4 | import android.content.res.Resources
5 | import android.graphics.Point
6 | import android.util.DisplayMetrics
7 | import android.util.Log
8 | import android.util.TypedValue
9 | import android.view.WindowManager
10 | import com.playsho.android.base.ApplicationLoader
11 | import kotlin.math.abs
12 | import kotlin.math.ceil
13 |
14 | /**
15 | * Utility class for handling display dimensions and density conversions.
16 | */
17 | object DimensionUtils {
18 |
19 | private const val TAG = "DimensionUtils"
20 |
21 | // Display properties
22 | var displaySize = Point()
23 | var firstConfigurationWas: Boolean = false
24 | var usingHardwareInput: Boolean = false
25 | var displayMetrics = DisplayMetrics()
26 | var screenRefreshRate: Float = 60f
27 | var density: Float = 1f
28 |
29 | /**
30 | * Retrieves the height of the display in pixels.
31 | *
32 | * @return The height of the display.
33 | */
34 | fun getDisplayHeightInPixel(): Int {
35 | return Resources.getSystem().displayMetrics.heightPixels
36 | }
37 |
38 | /**
39 | * Retrieves the width of the display in pixels.
40 | *
41 | * @return The width of the display.
42 | */
43 | fun getDisplayWidthInPixel(): Int {
44 | return Resources.getSystem().displayMetrics.widthPixels
45 | }
46 |
47 | /**
48 | * Converts density-independent pixels (dp) to pixels (px).
49 | *
50 | * @param value The value in dp to be converted.
51 | * @return The converted value in pixels.
52 | */
53 | fun dpToPx(value: Float): Int {
54 | if (value == 0f) {
55 | return 0
56 | }
57 | return ceil((density * value).toDouble()).toInt()
58 | }
59 |
60 | /**
61 | * Converts pixels (px) to density-independent pixels (dp).
62 | *
63 | * @param px The value in pixels to be converted.
64 | * @return The converted value in dp.
65 | */
66 | fun pxToDp(px: Int): Float {
67 | val metrics = ApplicationLoader.context.resources.displayMetrics
68 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px.toFloat(), metrics)
69 | }
70 |
71 | /**
72 | * Checks and updates the display size based on the given context and configuration.
73 | *
74 | * @param context The context.
75 | * @param newConfiguration The new configuration (can be null).
76 | */
77 | fun checkDisplaySize(context: Context, newConfiguration: Configuration?) {
78 | try {
79 | density = context.resources.displayMetrics.density
80 | firstConfigurationWas = true
81 | val configuration = newConfiguration ?: context.resources.configuration
82 | usingHardwareInput = configuration.keyboard != Configuration.KEYBOARD_NOKEYS && configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
83 | val manager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager?
84 | manager?.defaultDisplay?.let { display ->
85 | display.getMetrics(displayMetrics)
86 | display.getSize(displaySize)
87 | screenRefreshRate = display.refreshRate
88 | }
89 | if (configuration.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
90 | val newSize = (configuration.screenWidthDp * density + 0.5f).toInt()
91 | if (abs(displaySize.x - newSize) > 3) {
92 | displaySize.x = newSize
93 | }
94 | }
95 | if (configuration.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
96 | val newSize = (configuration.screenHeightDp * density + 0.5f).toInt()
97 | if (abs(displaySize.y - newSize) > 3) {
98 | displaySize.y = newSize
99 | }
100 | }
101 | } catch (e: Exception) {
102 | Log.e(TAG, "checkDisplaySize: ", e)
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/KeyStoreHelper.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import java.security.KeyStore
4 | import java.security.PrivateKey
5 | import java.security.PublicKey
6 | import java.security.cert.Certificate
7 |
8 | object KeyStoreHelper {
9 | const val KEY_PROVIDER = "AndroidKeyStore"
10 | private val keyStoreInstance: KeyStore by lazy {
11 | KeyStore.getInstance(KEY_PROVIDER).apply {
12 | loadKeyStore()
13 | }
14 | }
15 |
16 | object KeyAllies {
17 | const val RSA_KEYS = "rsa_key"
18 | }
19 |
20 | fun getInstance(): KeyStore {
21 | return keyStoreInstance
22 | }
23 |
24 | private fun KeyStore.loadKeyStore() {
25 | runCatching {
26 | load(null)
27 | }.onFailure {
28 | // Handle error appropriately
29 | it.printStackTrace()
30 | }
31 | }
32 |
33 | fun containsAlias(keyAlias: String): Boolean {
34 | return try {
35 | getInstance().containsAlias(keyAlias)
36 | } catch (e: Exception) {
37 | // Handle error appropriately
38 | e.printStackTrace()
39 | false
40 | }
41 | }
42 |
43 | inline fun getKey(keyAlias: String, pass: CharArray? = null): T {
44 | return try {
45 | when (T::class) {
46 | PrivateKey::class -> getInstance().getKey(keyAlias, pass) as T
47 | PublicKey::class -> getInstance().getCertificate(keyAlias)?.publicKey as T
48 | Certificate::class -> getInstance().getCertificate(keyAlias) as T
49 | else -> throw IllegalArgumentException("Unsupported key type")
50 | }
51 | } catch (e: Exception) {
52 | // Handle error appropriately
53 | e.printStackTrace()
54 | throw IllegalStateException("Error retrieving key")
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/LocalController.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Typeface
5 | import android.graphics.drawable.Drawable
6 | import android.os.Build
7 | import androidx.annotation.RequiresApi
8 | import androidx.core.content.ContextCompat
9 | import com.playsho.android.base.ApplicationLoader
10 |
11 | /**
12 | * Utility class for accessing local resources and assets.
13 | */
14 | object LocalController {
15 |
16 | /**
17 | * Retrieves the localized string from the resources.
18 | *
19 | * @param resourceId The resource ID of the string.
20 | * @return The localized string.
21 | */
22 | fun getString(resourceId: Int): String {
23 | return ApplicationLoader.context.resources.getString(resourceId)
24 | }
25 |
26 | /**
27 | * Retrieves a custom font from the resources using font resource ID.
28 | *
29 | * @param resourceId The resource ID of the font.
30 | * @return The Typeface object representing the custom font.
31 | */
32 | @RequiresApi(api = Build.VERSION_CODES.O)
33 | fun getFont(resourceId: Int): Typeface {
34 | return ApplicationLoader.context.resources.getFont(resourceId)
35 | }
36 |
37 | /**
38 | * Retrieves a custom font from the assets folder using the font name.
39 | *
40 | * @param fontName The name of the font file.
41 | * @return The Typeface object representing the custom font.
42 | */
43 | fun getFont(fontName: String): Typeface? {
44 | return try {
45 | Typeface.createFromAsset(ApplicationLoader.context.assets, "fonts/$fontName.ttf")
46 | } catch (e: Exception) {
47 | e.printStackTrace()
48 | null
49 | }
50 | }
51 |
52 | /**
53 | * Retrieves the color from the resources.
54 | *
55 | * @param resource The resource ID of the color.
56 | * @return The color value.
57 | */
58 | fun getColor(resource: Int): Int {
59 | return ContextCompat.getColor(ApplicationLoader.context, resource)
60 | }
61 |
62 | /**
63 | * Retrieves the dimension value from the resources.
64 | *
65 | * @param resource The resource ID of the dimension.
66 | * @return The dimension value.
67 | */
68 | fun getDimen(resource: Int): Float {
69 | return ApplicationLoader.context.resources.getDimension(resource)
70 | }
71 |
72 | /**
73 | * Retrieves the dimension pixel size from the resources.
74 | *
75 | * @param resource The resource ID of the dimension pixel size.
76 | * @return The dimension pixel size.
77 | */
78 | fun getDimensionPixelSize(resource: Int): Int {
79 | return ApplicationLoader.context.resources.getDimensionPixelSize(resource)
80 | }
81 |
82 | /**
83 | * Retrieves the drawable from the resources.
84 | *
85 | * @param resource The resource ID of the drawable.
86 | * @return The Drawable object.
87 | */
88 | fun getDrawable(resource: Int): Drawable? {
89 | return ContextCompat.getDrawable(ApplicationLoader.context, resource)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/NetworkListener.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.net.Network
6 | import android.net.NetworkCapabilities
7 | import androidx.annotation.NonNull
8 | import com.playsho.android.base.ApplicationLoader
9 | import java.util.concurrent.atomic.AtomicBoolean
10 |
11 | /**
12 | * NetworkListener is a utility class that monitors network availability changes using
13 | * ConnectivityManager.NetworkCallback. It provides methods to initialize the listener,
14 | * check the current network availability, and get real-time updates on network status.
15 | */
16 | class NetworkListener : ConnectivityManager.NetworkCallback() {
17 |
18 | // ConnectivityManager instance for network monitoring
19 | private val connectivityManager: ConnectivityManager = ApplicationLoader.context
20 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
21 |
22 | // AtomicBoolean to ensure thread-safe access to network availability status
23 | val isNetworkAvailable = AtomicBoolean(false)
24 |
25 | /**
26 | * Initializes the NetworkListener by registering it with the ConnectivityManager.
27 | * This method should be called to start monitoring network changes.
28 | */
29 | fun init() {
30 | // Register the NetworkListener to receive network status callbacks
31 | connectivityManager.registerDefaultNetworkCallback(this)
32 | }
33 |
34 | /**
35 | * Checks the current network availability status.
36 | *
37 | * @return True if the network is available, false otherwise.
38 | */
39 | fun checkNetworkAvailability(): Boolean {
40 | // Obtain the currently active network
41 | val network: Network? = connectivityManager.activeNetwork
42 |
43 | if (network == null) {
44 | // No active network, set availability to false
45 | isNetworkAvailable.set(false)
46 | return isNetworkAvailable()
47 | }
48 |
49 | // Obtain the network capabilities of the active network
50 | val networkCapabilities: NetworkCapabilities? = connectivityManager.getNetworkCapabilities(network)
51 |
52 | if (networkCapabilities == null) {
53 | // Network capabilities not available, set availability to false
54 | isNetworkAvailable.set(false)
55 | return isNetworkAvailable()
56 | }
57 |
58 | // Check if the network has any of the specified transport types (e.g., WiFi, Cellular)
59 | isNetworkAvailable.set(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
60 | || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
61 | || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
62 | || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH))
63 |
64 | return isNetworkAvailable()
65 | }
66 |
67 | /**
68 | * Gets the real-time network availability status.
69 | *
70 | * @return True if the network is available, false otherwise.
71 | */
72 | fun isNetworkAvailable(): Boolean {
73 | return isNetworkAvailable.get()
74 | }
75 |
76 | /**
77 | * Called when a network becomes available.
78 | *
79 | * @param network The Network object representing the available network.
80 | */
81 | override fun onAvailable(network: Network) {
82 | // Set network availability status to true
83 | isNetworkAvailable.set(true)
84 | }
85 |
86 | /**
87 | * Called when a network is lost or becomes unavailable.
88 | *
89 | * @param network The Network object representing the lost network.
90 | */
91 | override fun onLost(network: Network) {
92 | // Set network availability status to false
93 | isNetworkAvailable.set(false)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/PlayerUtils.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import kotlinx.coroutines.DelicateCoroutinesApi
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.GlobalScope
6 | import kotlinx.coroutines.launch
7 | import java.net.URL
8 | import java.net.URLConnection
9 |
10 | object PlayerUtils {
11 |
12 | @OptIn(DelicateCoroutinesApi::class)
13 | fun getUrlMimeType(url: String, callback: (String?) -> Unit) {
14 | GlobalScope.launch(Dispatchers.IO) {
15 | try {
16 | val connection = URL(url).openConnection()
17 | val mimeType = connection.contentType
18 | callback(mimeType)
19 | } catch (e: Exception) {
20 | callback(null)
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/RSAHelper.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.security.keystore.KeyGenParameterSpec
4 | import android.security.keystore.KeyProperties
5 | import android.util.Base64
6 | import java.nio.charset.StandardCharsets
7 | import java.security.KeyFactory
8 | import java.security.KeyPair
9 | import java.security.KeyPairGenerator
10 | import java.security.NoSuchAlgorithmException
11 | import java.security.PrivateKey
12 | import java.security.PublicKey
13 | import java.security.spec.InvalidKeySpecException
14 | import java.security.spec.PKCS8EncodedKeySpec
15 | import java.security.spec.X509EncodedKeySpec
16 | import javax.crypto.Cipher
17 |
18 | object RSAHelper {
19 | private const val CRYPTO_METHOD = "RSA"
20 |
21 | fun generateKeyPair(): KeyPair {
22 | val keyPairGenerator = KeyPairGenerator.getInstance(CRYPTO_METHOD, KeyStoreHelper.KEY_PROVIDER)
23 | val spec = KeyGenParameterSpec.Builder(
24 | KeyStoreHelper.KeyAllies.RSA_KEYS,
25 | KeyProperties.PURPOSE_SIGN
26 | or KeyProperties.PURPOSE_VERIFY
27 | or KeyProperties.PURPOSE_ENCRYPT
28 | or KeyProperties.PURPOSE_DECRYPT
29 | )
30 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
31 | .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
32 | .setDigests(KeyProperties.DIGEST_SHA256)
33 | .setKeySize(2048)
34 | .build()
35 | keyPairGenerator.initialize(spec)
36 | return keyPairGenerator.generateKeyPair()
37 | }
38 |
39 | fun getKeyPairs(): KeyPair {
40 |
41 | return if (!KeyStoreHelper.containsAlias(KeyStoreHelper.KeyAllies.RSA_KEYS)) {
42 | // Generate a new RSA key pair
43 | generateKeyPair()
44 | } else {
45 | // Load the existing RSA key pair
46 | val privateKey:PrivateKey = KeyStoreHelper.getKey(KeyStoreHelper.KeyAllies.RSA_KEYS)
47 | val publicKey:PublicKey = KeyStoreHelper.getKey(KeyStoreHelper.KeyAllies.RSA_KEYS)
48 | KeyPair(publicKey, privateKey)
49 | }
50 | }
51 |
52 | /*** Encrypt with Public Key ***/
53 | fun encrypt(
54 | textToEncrypt: String,
55 | publicKey: PublicKey
56 | ): String {
57 | val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
58 | cipher.init(Cipher.ENCRYPT_MODE, publicKey)
59 | val encryptedBytes = cipher.doFinal(textToEncrypt.toByteArray(StandardCharsets.UTF_8))
60 | return Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
61 | }
62 |
63 | /*** Encrypt with String Public Key ***/
64 | fun encrypt(
65 | textToEncrypt: String,
66 | publicKeyString: String
67 | ): String {
68 | val publicKey = stringToPublicKey(publicKeyString)
69 | return encrypt(
70 | textToEncrypt = textToEncrypt,
71 | publicKey = publicKey
72 | )
73 | }
74 |
75 | fun decrypt(
76 | encryptedText: String,
77 | privateKey: PrivateKey
78 | ): String {
79 | val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
80 | cipher.init(Cipher.DECRYPT_MODE, privateKey)
81 | val decryptedBytes = cipher.doFinal(Base64.decode(encryptedText, Base64.DEFAULT))
82 | return String(decryptedBytes)
83 | }
84 |
85 |
86 | /*** Prints Public Key String ***/
87 | fun printPublicKey(keyPairMap: KeyPair): String {
88 | val pemFormat = StringBuilder()
89 | pemFormat.append("-----BEGIN PUBLIC KEY-----\n")
90 | pemFormat.append(String(Base64.encode(keyPairMap.public.encoded, Base64.DEFAULT)))
91 | pemFormat.append("-----END PUBLIC KEY-----")
92 | return pemFormat.toString()
93 | }
94 |
95 | /*** Converts String Public Key to PublicKey Object ***/
96 | @Throws(InvalidKeySpecException::class, NoSuchAlgorithmException::class)
97 | private fun stringToPublicKey(publicKeyString: String): PublicKey {
98 | val keyBytes: ByteArray = Base64.decode(publicKeyString, Base64.DEFAULT)
99 | val spec = X509EncodedKeySpec(keyBytes)
100 | val keyFactory = KeyFactory.getInstance(CRYPTO_METHOD)
101 | return keyFactory.generatePublic(spec)
102 | }
103 |
104 | /*** Converts String Private Key to PrivateKey Object ***/
105 | @Throws(InvalidKeySpecException::class, NoSuchAlgorithmException::class)
106 | private fun stringToPrivateKey(privateKeyString: String): PrivateKey {
107 | val pkcs8EncodedBytes: ByteArray = Base64.decode(privateKeyString, Base64.DEFAULT)
108 | val keySpec = PKCS8EncodedKeySpec(pkcs8EncodedBytes)
109 | val kf = KeyFactory.getInstance(CRYPTO_METHOD)
110 | return kf.generatePrivate(keySpec)
111 | }
112 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/ThemeHelper.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.graphics.drawable.GradientDrawable
4 | import androidx.annotation.ColorRes
5 | import androidx.core.content.ContextCompat
6 | import com.playsho.android.base.ApplicationLoader
7 |
8 | /**
9 | * Helper class for creating GradientDrawable objects with various shapes and styles.
10 | */
11 | object ThemeHelper {
12 |
13 | /**
14 | * Creates a rectangular GradientDrawable with the specified color and radius.
15 | *
16 | * @param color The color resource ID for the rectangle.
17 | * @param radius The corner radius in dp for the rectangle.
18 | * @return A rectangular GradientDrawable.
19 | */
20 | fun createRect(@ColorRes color: Int, radius: Int): GradientDrawable {
21 | val gradientDrawable = GradientDrawable()
22 | gradientDrawable.shape = GradientDrawable.RECTANGLE
23 | gradientDrawable.cornerRadius = DimensionUtils.dpToPx(radius.toFloat()).toFloat()
24 | gradientDrawable.setColor(ContextCompat.getColor(ApplicationLoader.context, color))
25 | return gradientDrawable
26 | }
27 |
28 | /**
29 | * Creates a rectangular GradientDrawable with the specified color, radius, stroke color, and stroke width.
30 | *
31 | * @param color The color resource ID for the rectangle.
32 | * @param radius The corner radius in dp for the rectangle.
33 | * @param strokeColor The stroke color resource ID for the rectangle.
34 | * @param strokeWidth The stroke width in dp for the rectangle.
35 | * @return A rectangular GradientDrawable with stroke.
36 | */
37 | fun createRect(@ColorRes color: Int, radius: Int, @ColorRes strokeColor: Int, strokeWidth: Int): GradientDrawable {
38 | val gradientDrawable = createRect(color, radius)
39 | gradientDrawable.setStroke(
40 | DimensionUtils.dpToPx(strokeWidth.toFloat()).toInt(),
41 | ContextCompat.getColor(ApplicationLoader.context, strokeColor)
42 | )
43 |
44 | return gradientDrawable
45 | }
46 |
47 | fun createRect(
48 | @ColorRes color: Int,
49 | radius: Int,
50 | @ColorRes strokeColor: Int,
51 | strokeWidth: Int,
52 | dashWidth:Float,
53 | dashGap:Float,
54 | ): GradientDrawable {
55 | val gradientDrawable = createRect(color, radius)
56 | gradientDrawable.setStroke(
57 | DimensionUtils.dpToPx(strokeWidth.toFloat()),
58 | ContextCompat.getColor(ApplicationLoader.context, strokeColor),
59 | DimensionUtils.dpToPx(dashWidth).toFloat(),
60 | DimensionUtils.dpToPx(dashGap).toFloat()
61 | )
62 |
63 | return gradientDrawable
64 | }
65 |
66 | /**
67 | * Creates a circular GradientDrawable with the specified color.
68 | *
69 | * @param color The color resource ID for the circle.
70 | * @return A circular GradientDrawable.
71 | */
72 | fun createCircle(@ColorRes color: Int): GradientDrawable {
73 | val gradientDrawable = GradientDrawable()
74 | gradientDrawable.shape = GradientDrawable.OVAL
75 | gradientDrawable.setColor(ContextCompat.getColor(ApplicationLoader.context, color))
76 | return gradientDrawable
77 | }
78 |
79 | /**
80 | * Creates a circular GradientDrawable with the specified color, stroke color, and stroke width.
81 | *
82 | * @param color The color resource ID for the circle.
83 | * @param strokeColor The stroke color resource ID for the circle.
84 | * @param strokeWidth The stroke width in dp for the circle.
85 | * @return A circular GradientDrawable with stroke.
86 | */
87 | fun createCircle(@ColorRes color: Int, @ColorRes strokeColor: Int, strokeWidth: Int): GradientDrawable {
88 | val gradientDrawable = createCircle(color)
89 | gradientDrawable.setStroke(
90 | DimensionUtils.dpToPx(strokeWidth.toFloat()).toInt(),
91 | ContextCompat.getColor(ApplicationLoader.context, strokeColor)
92 | )
93 | return gradientDrawable
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/Validator.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils
2 |
3 | import android.util.Patterns
4 |
5 | /**
6 | * A utility class for performing various validation checks on strings and values.
7 | */
8 | object Validator {
9 |
10 | /**
11 | * Checks if a string is null or empty.
12 | *
13 | * @param string The string to check.
14 | * @return `true` if the string is null or empty, `false` otherwise.
15 | */
16 | fun isNullOrEmpty(string: String?): Boolean {
17 | return string.isNullOrEmpty()
18 | }
19 |
20 |
21 | fun isUrl(text: String): Boolean {
22 | val pattern = Patterns.WEB_URL
23 | val matcher = pattern.matcher(text)
24 | return matcher.matches()
25 | }
26 |
27 | /**
28 | * Checks if a reference is null.
29 | *
30 | * @param reference The reference to check.
31 | * @return `true` if the reference is null, `false` otherwise.
32 | */
33 | fun isNull(reference: T?): Boolean {
34 | return reference == null
35 | }
36 |
37 | /**
38 | * Checks if a string is a valid Iran phone number.
39 | *
40 | * @param phone The phone number to check.
41 | * @return `true` if the phone number is a valid Iran phone number, `false` otherwise.
42 | */
43 | fun isIranPhoneNumber(phone: String?): Boolean {
44 | return !phone.isNullOrEmpty() && (phone.startsWith("09") || phone.startsWith("+98")) && phone.length >= 11
45 | }
46 |
47 | /**
48 | * Checks if a string has a specific length.
49 | *
50 | * @param content The string to check.
51 | * @param length The expected length.
52 | * @return `true` if the string has the specified length, `false` otherwise.
53 | */
54 | fun hasLength(content: String?, length: Int): Boolean {
55 | return !content.isNullOrEmpty() && content.length == length
56 | }
57 |
58 | /**
59 | * Checks if the length of a string is greater than a specified value.
60 | *
61 | * @param content The string to check.
62 | * @param length The minimum length.
63 | * @return `true` if the string length is greater than the specified value, `false` otherwise.
64 | */
65 | fun isGreaterThan(content: String?, length: Int): Boolean {
66 | return !content.isNullOrEmpty() && content.length > length
67 | }
68 |
69 | /**
70 | * Gets the string value or a default if it is null or empty.
71 | *
72 | * @param s The string to check.
73 | * @param def The default value.
74 | * @return The original string if not null or empty, otherwise the default value.
75 | */
76 | fun getDefaultIfNullOrEmpty(s: String?, def: String): String {
77 | return s ?: def
78 | }
79 |
80 | /**
81 | * Checks if the length of a string is smaller than a specified value.
82 | *
83 | * @param content The string to check.
84 | * @param length The maximum length.
85 | * @return `true` if the string length is smaller than the specified value, `false` otherwise.
86 | */
87 | fun isSmallerThan(content: String?, length: Int): Boolean {
88 | return !content.isNullOrEmpty() && content.length < length
89 | }
90 |
91 | /**
92 | * Checks if two integers are equal.
93 | *
94 | * @param a The first integer.
95 | * @param b The second integer.
96 | * @return `true` if the integers are equal, `false` otherwise.
97 | */
98 | fun isEqual(a: Int, b: Int): Boolean {
99 | return a == b
100 | }
101 |
102 | /**
103 | * Checks if two strings are equal (handling null values).
104 | *
105 | * @param a The first string.
106 | * @param b The second string.
107 | * @return `true` if the strings are equal, `false` otherwise.
108 | */
109 | fun isEqual(a: String?, b: String?): Boolean {
110 | return a == b
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/accountmanager/AccountAuthenticator.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils.accountmanager
2 |
3 | import android.accounts.AbstractAccountAuthenticator
4 | import android.accounts.Account
5 | import android.accounts.AccountAuthenticatorResponse
6 | import android.accounts.NetworkErrorException
7 | import android.content.Context
8 | import android.os.Bundle
9 |
10 | /**
11 | * Custom Account Authenticator for managing user accounts.
12 | *
13 | * This class extends AbstractAccountAuthenticator and is responsible for handling various account-related
14 | * operations such as account creation, authentication, token retrieval, and more.
15 | *
16 | * Note: This class provides a skeleton implementation, and the actual functionality should be implemented
17 | * based on the specific requirements of your application.
18 | *
19 | * @see AbstractAccountAuthenticator
20 | */
21 | class AccountAuthenticator(context: Context) : AbstractAccountAuthenticator(context) {
22 |
23 | /**
24 | * Allows editing properties for the account.
25 | *
26 | * @param response The response to send the result back to the AccountManager.
27 | * @param accountType The account type.
28 | * @return A Bundle containing any additional information.
29 | */
30 | override fun editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? {
31 | // Implement if needed
32 | return null
33 | }
34 |
35 | /**
36 | * Adds a new account to the system.
37 | *
38 | * @param response The response to send the result back to the AccountManager.
39 | * @param accountType The account type.
40 | * @param authTokenType The authentication token type.
41 | * @param requiredFeatures An array of features the account must support.
42 | * @param options A Bundle containing additional options.
43 | * @return A Bundle containing the result of the account creation operation.
44 | * @throws NetworkErrorException If a network error occurs.
45 | */
46 | @Throws(NetworkErrorException::class)
47 | override fun addAccount(
48 | response: AccountAuthenticatorResponse,
49 | accountType: String,
50 | authTokenType: String?,
51 | requiredFeatures: Array?,
52 | options: Bundle?
53 | ): Bundle? {
54 | // Implement account creation here
55 | return null
56 | }
57 |
58 | override fun confirmCredentials(
59 | response: AccountAuthenticatorResponse?,
60 | account: Account?,
61 | options: Bundle?
62 | ): Bundle {
63 | TODO("Not yet implemented")
64 | }
65 |
66 | override fun getAuthToken(
67 | response: AccountAuthenticatorResponse?,
68 | account: Account?,
69 | authTokenType: String?,
70 | options: Bundle?
71 | ): Bundle {
72 | TODO("Not yet implemented")
73 | }
74 |
75 | override fun getAuthTokenLabel(authTokenType: String?): String {
76 | TODO("Not yet implemented")
77 | }
78 |
79 | override fun updateCredentials(
80 | response: AccountAuthenticatorResponse?,
81 | account: Account?,
82 | authTokenType: String?,
83 | options: Bundle?
84 | ): Bundle {
85 | TODO("Not yet implemented")
86 | }
87 |
88 | override fun hasFeatures(
89 | response: AccountAuthenticatorResponse?,
90 | account: Account?,
91 | features: Array?
92 | ): Bundle {
93 | TODO("Not yet implemented")
94 | }
95 |
96 | // Implement other methods similarly
97 |
98 | // Other methods have similar implementations
99 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/accountmanager/AccountAuthenticatorService.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils.accountmanager
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.IBinder
6 | import android.support.annotation.Nullable
7 | import com.playsho.android.base.ApplicationLoader
8 |
9 | /**
10 | * Service responsible for managing the Account Authenticator.
11 | *
12 | * This service extends the Android Service class and acts as a bridge between the Android
13 | * AccountManager and the custom AccountAuthenticator. It provides the IBinder to the
14 | * AccountAuthenticator, allowing the AccountManager to interact with it.
15 | */
16 | class AccountAuthenticatorService : Service() {
17 |
18 | /**
19 | * The instance of the custom AccountAuthenticator.
20 | */
21 | private val authenticator: AccountAuthenticator = AccountAuthenticator(ApplicationLoader.context)
22 |
23 | /**
24 | * Called when the service is created. Perform one-time initialization here.
25 | */
26 | override fun onCreate() {
27 | super.onCreate()
28 | // Perform any additional initialization if needed
29 | }
30 |
31 | /**
32 | * Called when the service is bound by the AccountManager.
33 | *
34 | * @param intent The intent that was used to bind to this service.
35 | * @return An IBinder interface to the service.
36 | */
37 | @Nullable
38 | override fun onBind(intent: Intent): IBinder? {
39 | /*
40 | * Return the IBinder interface to the AccountAuthenticator.
41 | * This allows the AccountManager to interact with the AccountAuthenticator.
42 | */
43 | return authenticator.iBinder
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/playsho/android/utils/accountmanager/AccountInstance.kt:
--------------------------------------------------------------------------------
1 | package com.playsho.android.utils.accountmanager
2 |
3 | import com.playsho.android.base.ApplicationLoader
4 |
5 | import android.accounts.Account
6 | import android.accounts.AccountManager
7 | import android.os.Bundle
8 | /**
9 | * The `AccountInstance` class provides utility methods for managing user accounts
10 | * using the Android `AccountManager`.
11 | */
12 | object AccountInstance {
13 |
14 | private val ACCOUNT_TYPE = ApplicationLoader.context.packageName
15 | private val accountManager: AccountManager = AccountManager.get(ApplicationLoader.context)
16 | private var currentAccount: Account? = null
17 |
18 | /**
19 | * Set the current active account.
20 | *
21 | * @param account The account to set as the current active account.
22 | */
23 | fun use(account: Account?) {
24 | currentAccount = account
25 | }
26 |
27 | /**
28 | * Get the current active account.
29 | *
30 | * @return The current active account.
31 | */
32 | fun getCurrentAccount(): Account? {
33 | return currentAccount
34 | }
35 |
36 | /**
37 | * Check if there is any account available.
38 | *
39 | * @return `true` if there is at least one account, otherwise `false`.
40 | */
41 | fun hasAnyAccount(): Boolean {
42 | return getAccounts().isNotEmpty()
43 | }
44 |
45 | /**
46 | * Update user data associated with a specific account.
47 | *
48 | * @param account The account to update user data for.
49 | * @param key The key for the user data.
50 | * @param value The new value for the user data.
51 | */
52 | fun updateAccountUserData(account: Account, key: String, value: String) {
53 | accountManager.setUserData(account, key, value)
54 | }
55 |
56 | /**
57 | * Update user data associated with the current active account.
58 | *
59 | * @param key The key for the user data.
60 | * @param value The new value for the user data.
61 | */
62 | fun updateAccountUserData(key: String, value: String) {
63 | currentAccount?.let { accountManager.setUserData(it, key, value) }
64 | }
65 |
66 | /**
67 | * Get user data associated with a specific account.
68 | *
69 | * @param account The account to get user data for.
70 | * @param key The key for the user data.
71 | * @return The value of the user data.
72 | */
73 | fun getUserData(account: Account, key: String): String? {
74 | return accountManager.getUserData(account, key)
75 | }
76 |
77 | /**
78 | * Get user data associated with the current active account.
79 | *
80 | * @param key The key for the user data.
81 | * @return The value of the user data.
82 | */
83 | fun getUserData(key: String): String? {
84 | return currentAccount?.let { accountManager.getUserData(it, key) }
85 | }
86 |
87 | /**
88 | * Get the authentication token for a specific account and token type.
89 | *
90 | * @param account The account to get the authentication token for.
91 | * @param tokenType The type of the authentication token.
92 | * @return The authentication token.
93 | */
94 | fun getAuthToken(account: Account, tokenType: String): String? {
95 | return accountManager.peekAuthToken(account, tokenType)
96 | }
97 |
98 | /**
99 | * Get the authentication token for the current active account and token type.
100 | *
101 | * @param tokenType The type of the authentication token.
102 | * @return The authentication token.
103 | */
104 | fun getAuthToken(tokenType: String): String {
105 | return currentAccount.let { accountManager.peekAuthToken(it, tokenType) }
106 | }
107 |
108 | /**
109 | * Initialize the `AccountInstance` by setting the first available account as the current active account.
110 | */
111 | fun init() {
112 | val accounts = getAccounts()
113 | if (accounts.isNotEmpty()) {
114 | use(accounts[0])
115 | }
116 | }
117 |
118 | /**
119 | * Get an array of all accounts of the specified type.
120 | *
121 | * @return An array of accounts.
122 | */
123 | fun getAccounts(): Array {
124 | return accountManager.getAccountsByType(ACCOUNT_TYPE)
125 | }
126 |
127 | /**
128 | * Remove an account by name.
129 | *
130 | * @param accountName The name of the account to remove.
131 | */
132 | fun removeAccount(accountName: String) {
133 | getAccounts().forEach { account ->
134 | if (account.name == accountName) {
135 | accountManager.removeAccountExplicitly(account)
136 | return@forEach
137 | }
138 | }
139 | }
140 |
141 | /**
142 | * Set the authentication token for a specific account and token type.
143 | *
144 | * @param account The account to set the authentication token for.
145 | * @param tokenType The type of the authentication token.
146 | * @param authToken The authentication token.
147 | */
148 | fun setAuthToken(account: Account, tokenType: String, authToken: String) {
149 | accountManager.setAuthToken(account, tokenType, authToken)
150 | }
151 |
152 | /**
153 | * Set the authentication token for the current active account and token type.
154 | *
155 | * @param tokenType The type of the authentication token.
156 | * @param authToken The authentication token.
157 | */
158 | fun setAuthToken(tokenType: String, authToken: String) {
159 | currentAccount?.let { accountManager.setAuthToken(it, tokenType, authToken) }
160 | }
161 |
162 | /**
163 | * Create a new account with the specified name, password, and optional bundle.
164 | *
165 | * @param name The name of the new account.
166 | * @param pass The password for the new account.
167 | * @param bundle An optional bundle of account details.
168 | * @return The newly created account.
169 | */
170 | fun createAccount(name: String, pass: String, bundle: Bundle?): Account {
171 | val account = Account(name, ACCOUNT_TYPE)
172 | accountManager.addAccountExplicitly(account, pass, bundle)
173 | return account
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_eye.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_left_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_left_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_logo_mini.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_message.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_send_rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_send_rocket.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_smiley.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-hdpi/ic_action_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_eye.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_left_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_left_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_logo_mini.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_message.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_send_rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_send_rocket.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_smiley.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-mdpi/ic_action_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/img_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-nodpi/img_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_eye.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_left_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_left_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_logo_mini.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_message.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_send_rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_send_rocket.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_smiley.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xhdpi/ic_action_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_eye.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_left_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_left_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_logo_mini.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_message.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_send_rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_send_rocket.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_smiley.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxhdpi/ic_action_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_eye.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_left_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_left_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_logo_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_logo_mini.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_message.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_send_rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_send_rocket.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_smiley.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/drawable-xxxhdpi/ic_action_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_top_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/chat_bubble_me.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/chat_bubble_sender.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/chat_bubble_system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/font/roboto_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/font/roboto_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/roboto_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/font/roboto_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/roboto_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/font/roboto_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_cinema.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
16 |
17 |
26 |
27 |
35 |
36 |
37 |
38 |
44 |
45 |
67 |
68 |
69 |
74 |
75 |
86 |
87 |
94 |
95 |
105 |
106 |
107 |
108 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_room.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
19 |
20 |
28 |
29 |
37 |
38 |
45 |
46 |
55 |
56 |
57 |
58 |
59 |
60 |
65 |
66 |
74 |
75 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
97 |
98 |
99 |
108 |
109 |
119 |
120 |
133 |
134 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
22 |
23 |
31 |
32 |
38 |
39 |
46 |
47 |
48 |
49 |
62 |
63 |
75 |
76 |
88 |
89 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet_add_link.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
19 |
20 |
29 |
30 |
46 |
47 |
48 |
57 |
58 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet_change_name.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
23 |
24 |
40 |
41 |
42 |
51 |
52 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet_join_room.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
24 |
25 |
41 |
42 |
43 |
52 |
53 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet_send_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
24 |
25 |
35 |
36 |
51 |
52 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_message_me.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_message_sender.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
20 |
21 |
28 |
29 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_message_system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_cinema_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #181B22
4 | #EEEFF0
5 | #F2F2F2
6 | #81858B
7 | #FFFFFF
8 | #0FABC6
9 | #0FABC6
10 | #E0E0E2
11 | #767981
12 | #254FC3
13 | #ffffff
14 | #767981
15 | #F7F7F8
16 | #CDCED1
17 | #777777
18 | #323641
19 | #1E222A
20 | #254FC3
21 | #424750
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 56dp
4 | 24dp
5 | 100dp
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #181B22
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Playsho
3 | Play and watch together
4 | Create a room
5 | Add friends
6 | Add stream link to play
7 | Type a message or link
8 | Add video link
9 | Paste or type video link
10 | You can add any video link from any website with stream support.
11 | Add and Play!
12 | 1- Create a room without any hassle!\n2- Drop in a video link like a pro!\n3- Bring your buddies onboard!\n4- Sit back, relax, and enjoy the show! 🍿✨ :)
13 | Play and watch togather
14 | Create a room
15 | 1- Create a room without any hassle!\n2- Drop in a video link like a pro!\n3- Bring your buddies onboard!\n4- Sit back, relax, and enjoy the show! 🍿✨ :)
16 | + Add friends
17 | + Add stream link to play
18 | Type a message or link
19 | Loading...
20 | Join Room
21 | Room code: Paste or type it here! 🍿
22 | You\'re just one step away from joining the fun. Simply paste or type your room code into the box below. Need help finding it? Look for the unique combination of letters and numbers in your invite link, then enter only the code part here. Let\'s dive into the movie magic together!
23 | Join
24 | Settings
25 | Account
26 | Your end-to-end encryption key
27 | Regenerate Keys
28 | Sign out
29 | Sign out from your account and register again
30 | 🔑 RSA key pair successfully regenerated! 🎉
31 | Ta-da! Your name has been successfully updated!
32 | 🔑 Oh no! Something went wrong while regenerating the RSA key pair. 🤯 Please try again later and we\'ll get it sorted out! 🛠️
33 | Enter your name (e.g., John Doe)
34 | We\'d love to get to know you better. 😊
35 | Save
36 | Hey there! Want to level up your experience with us? Throw in your full name! We promise to sprinkle some magic and make your time with us as awesome as a unicorn\'s birthday party! 🦄✨
37 | Oops! Looks like the room tag is missing. Let\'s fill in that blank space!
38 | Paste the stream link here
39 | Ready for some streaming shenanigans? Drop your stream link into the magic box below and let\'s kick off the ultimate movie night! Don\'t keep the flicks waiting, share the fun with your roomies now!
40 | Submit
41 | Hey there! It looks like you haven\'t entered a valid stream link. Give it another shot!
42 | Loading...
43 | Chat
44 | Send
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
16 |
17 |
25 |
26 |
34 |
35 |
36 |
44 |
45 |
53 |
54 |
62 |
63 |
71 |
72 |
80 |
81 |
89 |
90 |
98 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/authenticator.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/playsho/android/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.playsho.android;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.jetbrains.kotlin.android) apply false
5 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | kotlin.code.style=official
19 | # Enables namespacing of each library's R class so that its R class includes only the
20 | # resources declared in the library itself and none from the library's dependencies,
21 | # thereby reducing the size of the R class for that library
22 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.4.1"
3 | kotlin = "1.9.0"
4 | coreKtx = "1.13.1"
5 | junit = "4.13.2"
6 | junitVersion = "1.1.5"
7 | espressoCore = "3.5.1"
8 | appcompat = "1.6.1"
9 | material = "1.12.0"
10 | activity = "1.8.0"
11 | constraintlayout = "2.1.4"
12 |
13 | [libraries]
14 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
15 | junit = { group = "junit", name = "junit", version.ref = "junit" }
16 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
17 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
18 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
19 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
20 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
21 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
22 |
23 | [plugins]
24 | android-application = { id = "com.android.application", version.ref = "agp" }
25 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
26 |
27 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 26 11:07:30 IRST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/screenshots/screenshot_four.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/screenshots/screenshot_four.png
--------------------------------------------------------------------------------
/screenshots/screenshot_one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/screenshots/screenshot_one.png
--------------------------------------------------------------------------------
/screenshots/screenshot_three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/screenshots/screenshot_three.png
--------------------------------------------------------------------------------
/screenshots/screenshot_two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binaryb3ast/playsho/8142bd8f5b25c495db1f71328d0fff31d7d44eb2/screenshots/screenshot_two.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "Playsho"
23 | include(":app")
24 |
--------------------------------------------------------------------------------