add player
* fix aod stream parsing
This commit is contained in:
		| @ -37,14 +37,17 @@ dependencies { | |||||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' |     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' | ||||||
|     implementation 'androidx.core:core-ktx:1.3.2' |     implementation 'androidx.core:core-ktx:1.3.2' | ||||||
|     implementation 'androidx.appcompat:appcompat:1.2.0' |     implementation 'androidx.appcompat:appcompat:1.2.0' | ||||||
|     implementation 'com.google.android.material:material:1.2.1' |  | ||||||
|     implementation 'com.google.code.gson:gson:2.8.6' |  | ||||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.0.2' |     implementation 'androidx.constraintlayout:constraintlayout:2.0.2' | ||||||
|     implementation 'androidx.navigation:navigation-fragment:2.3.0' |     implementation 'androidx.navigation:navigation-fragment:2.3.0' | ||||||
|     implementation 'androidx.navigation:navigation-ui:2.3.0' |     implementation 'androidx.navigation:navigation-ui:2.3.0' | ||||||
|     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' |     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' | ||||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' |     implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' | ||||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' |     implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' | ||||||
|  |     implementation 'androidx.security:security-crypto:1.1.0-alpha02' | ||||||
|  |  | ||||||
|  |     implementation 'com.google.android.material:material:1.2.1' | ||||||
|  |     implementation 'com.google.code.gson:gson:2.8.6' | ||||||
|  |  | ||||||
|     implementation 'org.jsoup:jsoup:1.13.1' |     implementation 'org.jsoup:jsoup:1.13.1' | ||||||
|     implementation 'com.github.bumptech.glide:glide:4.11.0' |     implementation 'com.github.bumptech.glide:glide:4.11.0' | ||||||
| @ -52,6 +55,11 @@ dependencies { | |||||||
|     implementation 'androidx.recyclerview:recyclerview:1.1.0' |     implementation 'androidx.recyclerview:recyclerview:1.1.0' | ||||||
|     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' |     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' | ||||||
|  |  | ||||||
|  |     implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0' | ||||||
|  |     implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.0' | ||||||
|  |     implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0' | ||||||
|  |     implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0' | ||||||
|  |  | ||||||
|     testImplementation 'junit:junit:4.12' |     testImplementation 'junit:junit:4.12' | ||||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.2' |     androidTestImplementation 'androidx.test.ext:junit:1.1.2' | ||||||
|     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' |     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
|         android:roundIcon="@mipmap/ic_launcher_round" |         android:roundIcon="@mipmap/ic_launcher_round" | ||||||
|         android:supportsRtl="true" |         android:supportsRtl="true" | ||||||
|         android:theme="@style/AppTheme"> |         android:theme="@style/AppTheme"> | ||||||
|  |         <activity android:name=".PlayerActivity"></activity> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".MainActivity" |             android:name=".MainActivity" | ||||||
|             android:label="@string/app_name"> |             android:label="@string/app_name"> | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| package org.mosad.teapod | package org.mosad.teapod | ||||||
|  |  | ||||||
|  | import android.content.Intent | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
|  | import android.util.Log | ||||||
| import android.view.MenuItem | import android.view.MenuItem | ||||||
| import com.google.android.material.bottomnavigation.BottomNavigationView | import com.google.android.material.bottomnavigation.BottomNavigationView | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| @ -8,6 +10,7 @@ import androidx.fragment.app.Fragment | |||||||
| import androidx.fragment.app.commit | import androidx.fragment.app.commit | ||||||
| import kotlinx.android.synthetic.main.activity_main.* | import kotlinx.android.synthetic.main.activity_main.* | ||||||
| import org.mosad.teapod.parser.AoDParser | import org.mosad.teapod.parser.AoDParser | ||||||
|  | import org.mosad.teapod.preferences.EncryptedPreferences | ||||||
| import org.mosad.teapod.ui.MediaFragment | import org.mosad.teapod.ui.MediaFragment | ||||||
| import org.mosad.teapod.ui.account.AccountFragment | import org.mosad.teapod.ui.account.AccountFragment | ||||||
| import org.mosad.teapod.ui.home.HomeFragment | import org.mosad.teapod.ui.home.HomeFragment | ||||||
| @ -69,7 +72,14 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun load() { |     private fun load() { | ||||||
|         // TODO |         EncryptedPreferences.readCredentials(this) | ||||||
|  |  | ||||||
|  |         if (EncryptedPreferences.password.isEmpty()) { | ||||||
|  |             Log.i(javaClass.name, "please login!") | ||||||
|  |  | ||||||
|  |             // TODO show login dialog | ||||||
|  |             //EncryptedPreferences.saveCredentials("", "", this) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun showDetailFragment(media: GUIMedia) { |     fun showDetailFragment(media: GUIMedia) { | ||||||
| @ -85,4 +95,11 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS | |||||||
|         println("visible !!!: " + mediaFragment.isVisible) |         println("visible !!!: " + mediaFragment.isVisible) | ||||||
|         println(supportFragmentManager.backStackEntryCount) |         println(supportFragmentManager.backStackEntryCount) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun startPlayer(streamUrl: String) { | ||||||
|  |         val intent = Intent(this, PlayerActivity::class.java).apply { | ||||||
|  |             putExtra("streamUrl", streamUrl) | ||||||
|  |         } | ||||||
|  |         startActivity(intent) | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										107
									
								
								app/src/main/java/org/mosad/teapod/PlayerActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								app/src/main/java/org/mosad/teapod/PlayerActivity.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | package org.mosad.teapod | ||||||
|  |  | ||||||
|  | import android.net.Uri | ||||||
|  | import android.os.Bundle | ||||||
|  | import android.view.View | ||||||
|  | import androidx.appcompat.app.AppCompatActivity | ||||||
|  | import com.google.android.exoplayer2.ExoPlayer | ||||||
|  | import com.google.android.exoplayer2.MediaItem | ||||||
|  | import com.google.android.exoplayer2.Player | ||||||
|  | import com.google.android.exoplayer2.SimpleExoPlayer | ||||||
|  | import com.google.android.exoplayer2.source.hls.HlsMediaSource | ||||||
|  | import com.google.android.exoplayer2.upstream.DataSource | ||||||
|  | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory | ||||||
|  | import com.google.android.exoplayer2.util.Util | ||||||
|  | import kotlinx.android.synthetic.main.activity_player.* | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PlayerActivity : AppCompatActivity() { | ||||||
|  |  | ||||||
|  |     private lateinit var player: SimpleExoPlayer | ||||||
|  |     private lateinit var dataSourceFactory: DataSource.Factory | ||||||
|  |  | ||||||
|  |     private var streamUrl = "" | ||||||
|  |  | ||||||
|  |     private var playWhenReady = true | ||||||
|  |     private var currentWindow = 0 | ||||||
|  |     private var playbackPosition: Long = 0 | ||||||
|  |  | ||||||
|  |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|  |         super.onCreate(savedInstanceState) | ||||||
|  |         setContentView(R.layout.activity_player) | ||||||
|  |  | ||||||
|  |         streamUrl = intent.getStringExtra("streamUrl").toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     override fun onStart() { | ||||||
|  |         super.onStart() | ||||||
|  |         if (Util.SDK_INT > 23) { | ||||||
|  |             initPlayer() | ||||||
|  |             if (video_view != null) video_view.onResume() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onResume() { | ||||||
|  |         super.onResume() | ||||||
|  |         if (Util.SDK_INT <= 23) { | ||||||
|  |             initPlayer() | ||||||
|  |             if (video_view != null) video_view.onResume() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onPause() { | ||||||
|  |         super.onPause() | ||||||
|  |         if (Util.SDK_INT <= 23) { | ||||||
|  |             if (video_view != null) video_view.onPause() | ||||||
|  |             releasePlayer() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onStop() { | ||||||
|  |         super.onStop() | ||||||
|  |         if (Util.SDK_INT > 23) { | ||||||
|  |             if (video_view != null) video_view.onPause() | ||||||
|  |             releasePlayer() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun initPlayer() { | ||||||
|  |         if (streamUrl.isEmpty()) { | ||||||
|  |             println("no streamUrl set") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         player = SimpleExoPlayer.Builder(this).build() | ||||||
|  |         dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod")) | ||||||
|  |  | ||||||
|  |         val mediaSource = HlsMediaSource.Factory(dataSourceFactory) | ||||||
|  |             .createMediaSource(MediaItem.fromUri(Uri.parse(streamUrl))) | ||||||
|  |  | ||||||
|  |         player.setMediaSource(mediaSource) | ||||||
|  |         player.prepare() | ||||||
|  |         player.play() | ||||||
|  |  | ||||||
|  |         player.addListener(object : Player.EventListener { | ||||||
|  |             override fun onPlaybackStateChanged(state: Int) { | ||||||
|  |                 super.onPlaybackStateChanged(state) | ||||||
|  |  | ||||||
|  |                 loading.visibility = when (state) { | ||||||
|  |                     ExoPlayer.STATE_READY ->  View.GONE | ||||||
|  |                     ExoPlayer.STATE_BUFFERING -> View.VISIBLE | ||||||
|  |                     else -> View.GONE | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         video_view.player = player | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun releasePlayer(){ | ||||||
|  |         playbackPosition = player.currentPosition | ||||||
|  |         currentWindow = player.currentWindowIndex | ||||||
|  |         playWhenReady = player.playWhenReady | ||||||
|  |         player.release() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -1,57 +0,0 @@ | |||||||
| package org.mosad.teapod.dummy |  | ||||||
|  |  | ||||||
| import java.util.ArrayList |  | ||||||
| import java.util.HashMap |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Helper class for providing sample content for user interfaces created by |  | ||||||
|  * Android template wizards. |  | ||||||
|  * |  | ||||||
|  * TODO: Replace all uses of this class before publishing your app. |  | ||||||
|  */ |  | ||||||
| object DummyContent { |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * An array of sample (dummy) items. |  | ||||||
|      */ |  | ||||||
|     val ITEMS: MutableList<DummyItem> = ArrayList() |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * A map of sample (dummy) items, by ID. |  | ||||||
|      */ |  | ||||||
|     val ITEM_MAP: MutableMap<String, DummyItem> = HashMap() |  | ||||||
|  |  | ||||||
|     private val COUNT = 25 |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         // Add some sample items. |  | ||||||
|         for (i in 1..COUNT) { |  | ||||||
|             addItem(createDummyItem(i)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun addItem(item: DummyItem) { |  | ||||||
|         ITEMS.add(item) |  | ||||||
|         ITEM_MAP.put(item.id, item) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun createDummyItem(position: Int): DummyItem { |  | ||||||
|         return DummyItem(position.toString(), "Item " + position, makeDetails(position)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun makeDetails(position: Int): String { |  | ||||||
|         val builder = StringBuilder() |  | ||||||
|         builder.append("Details about Item: ").append(position) |  | ||||||
|         for (i in 0..position - 1) { |  | ||||||
|             builder.append("\nMore details information here.") |  | ||||||
|         } |  | ||||||
|         return builder.toString() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * A dummy item representing a piece of content. |  | ||||||
|      */ |  | ||||||
|     data class DummyItem(val id: String, val content: String, val details: String) { |  | ||||||
|         override fun toString(): String = content |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -4,16 +4,14 @@ import com.google.gson.JsonParser | |||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
| import org.jsoup.Connection | import org.jsoup.Connection | ||||||
| import org.jsoup.Jsoup | import org.jsoup.Jsoup | ||||||
|  | import org.mosad.teapod.preferences.EncryptedPreferences | ||||||
| import org.mosad.teapod.util.GUIMedia | import org.mosad.teapod.util.GUIMedia | ||||||
|  |  | ||||||
| class AoDParser { | class AoDParser { | ||||||
|  |  | ||||||
|     private val baseURL = "https://www.anime-on-demand.de" |     private val baseURL = "https://www.anime-on-demand.de" | ||||||
|     private val loginPath = "/users/sign_in" |     private val loginPath = "/users/sign_in" | ||||||
|  |     private val libraryPath = "/animes" | ||||||
|     // TODO |  | ||||||
|     private val login = "" |  | ||||||
|     private val pwd = "" |  | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         private var sessionCookies = mutableMapOf<String, String>() |         private var sessionCookies = mutableMapOf<String, String>() | ||||||
| @ -39,8 +37,8 @@ class AoDParser { | |||||||
|             println("cookies: $cookies") |             println("cookies: $cookies") | ||||||
|  |  | ||||||
|             val data = mapOf( |             val data = mapOf( | ||||||
|                 Pair("user[login]", login), |                 Pair("user[login]", EncryptedPreferences.login), | ||||||
|                 Pair("user[password]", pwd), |                 Pair("user[password]", EncryptedPreferences.password), | ||||||
|                 Pair("user[remember_me]", "1"), |                 Pair("user[remember_me]", "1"), | ||||||
|                 Pair("commit", "Einloggen"), |                 Pair("commit", "Einloggen"), | ||||||
|                 Pair("authenticity_token", authenticityToken) |                 Pair("authenticity_token", authenticityToken) | ||||||
| @ -67,7 +65,7 @@ class AoDParser { | |||||||
|         if (sessionCookies.isEmpty()) login() |         if (sessionCookies.isEmpty()) login() | ||||||
|  |  | ||||||
|         withContext(Dispatchers.Default) { |         withContext(Dispatchers.Default) { | ||||||
|             val resAnimes = Jsoup.connect("$baseURL/animes") |             val resAnimes = Jsoup.connect(baseURL + libraryPath) | ||||||
|                 .cookies(sessionCookies) |                 .cookies(sessionCookies) | ||||||
|                 .get() |                 .get() | ||||||
|  |  | ||||||
| @ -147,7 +145,7 @@ class AoDParser { | |||||||
|                 .get("sources").asJsonArray |                 .get("sources").asJsonArray | ||||||
|  |  | ||||||
|             return@withContext sources.toList().map { |             return@withContext sources.toList().map { | ||||||
|                 it.asJsonObject.get("file").toString() |                 it.asJsonObject.get("file").asString | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -0,0 +1,66 @@ | |||||||
|  | package org.mosad.teapod.preferences | ||||||
|  |  | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.SharedPreferences | ||||||
|  | import android.security.keystore.KeyGenParameterSpec | ||||||
|  | import android.security.keystore.KeyProperties | ||||||
|  | import android.util.Log | ||||||
|  | import androidx.security.crypto.EncryptedSharedPreferences | ||||||
|  | import androidx.security.crypto.MasterKey | ||||||
|  | import org.mosad.teapod.R | ||||||
|  |  | ||||||
|  | object EncryptedPreferences { | ||||||
|  |  | ||||||
|  |     var login = "" | ||||||
|  |         internal set | ||||||
|  |     var password = "" | ||||||
|  |         internal set | ||||||
|  |  | ||||||
|  |     fun saveCredentials(login: String, password: String, context: Context) { | ||||||
|  |         this.login = login | ||||||
|  |         this.password = password | ||||||
|  |  | ||||||
|  |         with(getEncryptedPreferences(context)?.edit()) { | ||||||
|  |             this?.putString(context.getString(R.string.save_key_user_login), login) | ||||||
|  |             this?.putString(context.getString(R.string.save_key_user_password), password) | ||||||
|  |             this?.apply() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun readCredentials(context: Context) { | ||||||
|  |         with(getEncryptedPreferences(context)) { | ||||||
|  |             login = this?.getString(context.getString(R.string.save_key_user_login), "").toString() | ||||||
|  |             password = this?.getString(context.getString(R.string.save_key_user_password), "").toString() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * create a encrypted shared preference | ||||||
|  |      */ | ||||||
|  |     private fun getEncryptedPreferences(context: Context): SharedPreferences? { | ||||||
|  |         return try { | ||||||
|  |             val spec = KeyGenParameterSpec.Builder( | ||||||
|  |                 MasterKey.DEFAULT_MASTER_KEY_ALIAS, | ||||||
|  |                 KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) | ||||||
|  |                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) | ||||||
|  |                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) | ||||||
|  |                 .setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             val masterKey = MasterKey.Builder(context) | ||||||
|  |                 .setKeyGenParameterSpec(spec) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             EncryptedSharedPreferences.create( | ||||||
|  |                 context, | ||||||
|  |                 context.getString(R.string.encrypted_preference_file_key), | ||||||
|  |                 masterKey, | ||||||
|  |                 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, | ||||||
|  |                 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM | ||||||
|  |             ) | ||||||
|  |         } catch (ex: Exception) { | ||||||
|  |             Log.e(javaClass.name, "Could not create encrypted shared preference.", ex) | ||||||
|  |             null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -7,8 +7,11 @@ import android.view.View | |||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import com.bumptech.glide.Glide | import com.bumptech.glide.Glide | ||||||
| import kotlinx.android.synthetic.main.fragment_media.* | import kotlinx.android.synthetic.main.fragment_media.* | ||||||
|  | import org.mosad.teapod.MainActivity | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
| import org.mosad.teapod.util.GUIMedia | import org.mosad.teapod.util.GUIMedia | ||||||
|  | import java.net.URL | ||||||
|  | import java.net.URLEncoder | ||||||
|  |  | ||||||
| class MediaFragment(val media: GUIMedia, val streams: List<String>) : Fragment() { | class MediaFragment(val media: GUIMedia, val streams: List<String>) : Fragment() { | ||||||
|  |  | ||||||
| @ -36,7 +39,8 @@ class MediaFragment(val media: GUIMedia, val streams: List<String>) : Fragment() | |||||||
|     private fun onClickButtonPlay() { |     private fun onClickButtonPlay() { | ||||||
|         println("play ${streams.first()}") |         println("play ${streams.first()}") | ||||||
|  |  | ||||||
|  |         val mainActivity = activity as MainActivity | ||||||
|  |         mainActivity.startPlayer(streams.first()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
							
								
								
									
										23
									
								
								app/src/main/res/layout/activity_player.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/src/main/res/layout/activity_player.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     android:background="#000000" | ||||||
|  |     tools:context=".PlayerActivity"> | ||||||
|  |  | ||||||
|  |     <com.google.android.exoplayer2.ui.PlayerView | ||||||
|  |         android:id="@+id/video_view" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:layout_gravity="center" /> | ||||||
|  |     <!--    app:controller_layout_id="@layout/player_custom_control"/>--> | ||||||
|  |  | ||||||
|  |     <ProgressBar | ||||||
|  |         android:id="@+id/loading" | ||||||
|  |         android:layout_width="70dp" | ||||||
|  |         android:layout_height="70dp" | ||||||
|  |         android:layout_gravity="center" /> | ||||||
|  |  | ||||||
|  | </FrameLayout> | ||||||
| @ -4,4 +4,9 @@ | |||||||
|     <string name="title_library">Library</string> |     <string name="title_library">Library</string> | ||||||
|     <string name="title_search">Search</string> |     <string name="title_search">Search</string> | ||||||
|     <string name="title_account">Account</string> |     <string name="title_account">Account</string> | ||||||
|  |  | ||||||
|  |     <!-- save keys --> | ||||||
|  |     <string name="encrypted_preference_file_key" translatable="false">org.mosad.teapod.encrypted_preferences</string> | ||||||
|  |     <string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string> | ||||||
|  |     <string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string> | ||||||
| </resources> | </resources> | ||||||
		Reference in New Issue
	
	Block a user