diff --git a/.gitignore b/.gitignore
index aa724b7..38841db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,211 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin,android,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,kotlin,android,gradle
+
+### Android ###
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Log/OS Files
+*.log
+
+# Android Studio generated files and folders
+captures/
+.externalNativeBuild/
+.cxx/
+*.apk
+output.json
+
+# IntelliJ
*.iml
+.idea/
+
+# Keystore files
+*.jks
+*.keystore
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Android Profiling
+*.hprof
+
+### Android Patch ###
+gen-external-apklibs
+
+# Replacement of .externalNativeBuild directories introduced
+# with Android Studio 3.5.
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Gradle ###
.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
-.DS_Store
-/build
-/captures
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+### AndroidStudio ###
+# Covers files to be ignored for android development using Android Studio.
+
+# Built application files
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+
+# Signing files
+.signing/
+
+# Local configuration file (sdk path, etc)
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+
+# Android Studio
+/*/build/
+/*/local.properties
+/*/out
+/*/*/build
+/*/*/production
+.navigation/
+*.ipr
+*~
+*.swp
+
+# Keystore files
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Android Patch
+
+# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
-.cxx
-local.properties
+
+# NDK
+obj/
+
+# IntelliJ IDEA
+*.iws
+/out/
+
+# User-specific configurations
+.idea/caches/
+.idea/libraries/
+.idea/shelf/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/.name
+.idea/compiler.xml
+.idea/copyright/profiles_settings.xml
+.idea/encodings.xml
+.idea/misc.xml
+.idea/modules.xml
+.idea/scopes/scope_settings.xml
+.idea/dictionaries
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
+.idea/datasources.xml
+.idea/dataSources.ids
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+.idea/assetWizardSettings.xml
+.idea/gradle.xml
+.idea/jarRepositories.xml
+.idea/navEditor.xml
+
+# OS-specific files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Legacy Eclipse project files
+.classpath
+.project
+.cproject
+.settings/
+
+# Mobile Tools for Java (J2ME)
+
+# Package Files #
+
+# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
+
+## Plugin-specific files:
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Mongo Explorer plugin
+.idea/mongoSettings.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### AndroidStudio Patch ###
+
+!/gradle/wrapper/gradle-wrapper.jar
+
+# End of https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin,android,gradle
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 034a122..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Twitimer
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 26ec138..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index f861765..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 625b371..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index e34606c..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index ca38e7f..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index b5c8082..d6c1e99 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,8 +13,8 @@ android {
applicationId "com.mouredev.twitimer"
minSdkVersion 23
targetSdkVersion 30
- versionCode 15
- versionName "1.2.3"
+ versionCode 16
+ versionName "1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/java/com/mouredev/twitimer/model/domain/DatabaseUser.kt b/app/src/main/java/com/mouredev/twitimer/model/domain/DatabaseUser.kt
index c98f734..ea5d40a 100644
--- a/app/src/main/java/com/mouredev/twitimer/model/domain/DatabaseUser.kt
+++ b/app/src/main/java/com/mouredev/twitimer/model/domain/DatabaseUser.kt
@@ -56,6 +56,8 @@ data class DatabaseUserSchedule(
}
data class DatabaseUserSettings(
+
+ var onHolidays: Int? = null,
val discord: String? = null,
val youtube: String? = null,
val twitter: String? = null,
@@ -64,7 +66,7 @@ data class DatabaseUserSettings(
) {
fun toUserSettings(): UserSettings {
- return UserSettings(discord, youtube, twitter, instagram, tiktok)
+ return UserSettings(onHolidays == 1, discord, youtube, twitter, instagram, tiktok)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mouredev/twitimer/model/domain/User.kt b/app/src/main/java/com/mouredev/twitimer/model/domain/User.kt
index 2443760..111b0f0 100644
--- a/app/src/main/java/com/mouredev/twitimer/model/domain/User.kt
+++ b/app/src/main/java/com/mouredev/twitimer/model/domain/User.kt
@@ -76,7 +76,7 @@ data class User(
return scheduleJSON
}
- fun settingsToJSON(): MutableMap {
+ fun settingsToJSON(): MutableMap {
return settings?.toJSON() ?: mutableMapOf()
}
@@ -280,6 +280,7 @@ enum class WeekdayType(val index: Int) {
}
data class UserSettings(
+ var onHolidays: Boolean? = null,
var discord: String? = null,
var youtube: String? = null,
var twitter: String? = null,
@@ -287,9 +288,10 @@ data class UserSettings(
var tiktok: String? = null
) {
- fun toJSON(): MutableMap {
+ fun toJSON(): MutableMap {
return mutableMapOf(
+ DatabaseField.ON_HOLIDAYS.key to (if (onHolidays == true) 1 else 0),
DatabaseField.DISCORD.key to (discord ?: ""),
DatabaseField.YOUTUBE.key to (youtube ?: ""),
DatabaseField.TWITTER.key to (twitter ?: ""),
diff --git a/app/src/main/java/com/mouredev/twitimer/model/session/Session.kt b/app/src/main/java/com/mouredev/twitimer/model/session/Session.kt
index 7c962d2..50627b7 100644
--- a/app/src/main/java/com/mouredev/twitimer/model/session/Session.kt
+++ b/app/src/main/java/com/mouredev/twitimer/model/session/Session.kt
@@ -125,6 +125,17 @@ class Session {
}
}
+ fun delete(context: Context, success: () -> Unit) {
+
+ token?.accessToken?.let { accessToken ->
+ TwitchService.revoke(accessToken, {
+ remove(context, success)
+ }, {
+ remove(context, success)
+ })
+ }
+ }
+
fun save(context: Context, schedule: MutableList) {
val savedSchedule = savedSchedule(context)
@@ -178,6 +189,9 @@ class Session {
user?.followedUsers = mutableListOf()
}
user?.followedUsers?.add(login)
+ if (streamers == null) {
+ streamers = mutableListOf()
+ }
streamers?.add(followedUser)
setupNotification(true, login)
@@ -268,22 +282,25 @@ class Session {
streamers.forEach { streamer ->
- var nextSchedule: UserSchedule? = null
+ if (streamer.settings?.onHolidays == false) {
+
+ var nextSchedule: UserSchedule? = null
- streamer.schedule?.forEach { schedule ->
+ streamer.schedule?.forEach { schedule ->
- if (schedule.enable) {
+ if (schedule.enable) {
- val weekDate = schedule.weekDate()
+ val weekDate = schedule.weekDate()
- if ((nextSchedule == null && weekDate > currentDate) || (weekDate > currentDate && weekDate < nextSchedule!!.date)) {
- nextSchedule = schedule
+ if ((nextSchedule == null && weekDate > currentDate) || (weekDate > currentDate && weekDate < nextSchedule!!.date)) {
+ nextSchedule = schedule
+ }
}
}
- }
- nextSchedule?.let {
- sortedStreamings.add(SortedStreaming(streamer, it))
+ nextSchedule?.let {
+ sortedStreamings.add(SortedStreaming(streamer, it))
+ }
}
}
@@ -507,6 +524,17 @@ class Session {
}
}
+ private fun remove(context: Context, completion: (() -> Unit)) {
+
+ user?.let { user ->
+ FirebaseRDBService.delete(user, {
+ clear(context, completion)
+ }, {
+ clear(context, completion)
+ })
+ }
+ }
+
private fun clear(context: Context, completion: (() -> Unit)) {
user?.followedUsers?.forEach { user ->
diff --git a/app/src/main/java/com/mouredev/twitimer/provider/services/firebase/FirebaseRDBService.kt b/app/src/main/java/com/mouredev/twitimer/provider/services/firebase/FirebaseRDBService.kt
index 51c7470..57e4efb 100644
--- a/app/src/main/java/com/mouredev/twitimer/provider/services/firebase/FirebaseRDBService.kt
+++ b/app/src/main/java/com/mouredev/twitimer/provider/services/firebase/FirebaseRDBService.kt
@@ -28,7 +28,7 @@ enum class DatabaseField(val key: String) {
FOLLOWED_USERS("followedUsers"),
// Settings
- SETTINGS("settings"), DISCORD("discord"), YOUTUBE("youtube"), TWITTER("twitter"), INSTAGRAM("instagram"), TIKTOK("tiktok")
+ SETTINGS("settings"), ON_HOLIDAYS("onHolidays"), DISCORD("discord"), YOUTUBE("youtube"), TWITTER("twitter"), INSTAGRAM("instagram"), TIKTOK("tiktok")
}
@@ -109,7 +109,7 @@ object FirebaseRDBService {
}.addOnFailureListener {
failure()
}
- }?: kotlin.run {
+ } ?: run {
failure()
}
}
@@ -148,6 +148,23 @@ object FirebaseRDBService {
}
}
+
+
+ fun delete(user: User, success: () -> Unit, failure: () -> Unit) {
+
+ user.login?.let { login ->
+ (if (user.streamer == true) streamersRef else usersRef).child(login).removeValue { error, _ ->
+ if (error != null) {
+ failure()
+ } else {
+ success()
+ }
+ }
+ } ?: run {
+ failure()
+ }
+ }
+
// MARK: Private
private fun checkStreamersSearch(ids: List, finishedRequests: Int, streamers: List?, completion: (streamers: List?) -> Unit) {
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserFragment.kt b/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserFragment.kt
index b9d9fc7..081179b 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserFragment.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserFragment.kt
@@ -37,6 +37,8 @@ class UserFragment : Fragment() {
private lateinit var viewModel: UserViewModel
private var infoFragment: InfoFragment? = null
+ private var schedules: MutableList? = null
+
// Initialization
override fun onCreateView(
@@ -75,7 +77,7 @@ class UserFragment : Fragment() {
super.onResume()
// Setup
- setupHeader()
+ setupContent()
}
// Private
@@ -89,15 +91,7 @@ class UserFragment : Fragment() {
private fun setup() {
- val transaction = activity?.supportFragmentManager?.beginTransaction()
-
- val schedules = viewModel.getFilterSchedule()
- infoFragment = if (schedules.isNullOrEmpty()) InfoRouter().fragment(InfoViewType.SCHEDULE) else InfoRouter().fragment(InfoViewType.STREAMER)
- infoFragment?.let {
- transaction?.replace(R.id.frameLayoutInfo, it)
- }
- transaction?.disallowAddToBackStack()
- transaction?.commit()
+ schedules = viewModel.getFilterSchedule()
context?.let { context ->
@@ -116,9 +110,12 @@ class UserFragment : Fragment() {
binding.switchStreamer.setOnCheckedChangeListener { _, isChecked ->
if (viewModel.isStreamer != isChecked) {
viewModel.save(context, isChecked)
- setupBody(schedules)
- setupHeader()
- setupButtons()
+ setupContent()
+ if (isChecked) {
+ UIUtil.showAlert(context, getString(viewModel.syncInfoAlertTitleText), getString(viewModel.syncInfoAlertBodyText), getString(viewModel.okText), {
+ checkShowScheduleAlert(context)
+ })
+ }
}
}
@@ -139,20 +136,11 @@ class UserFragment : Fragment() {
// Recycler view
binding.recyclerViewSchedule.layoutManager = LinearLayoutManager(context)
- binding.recyclerViewSchedule.adapter = ScheduleRecyclerViewAdapter(context, schedules, viewModel.readOnly) { schedule ->
+ binding.recyclerViewSchedule.adapter = ScheduleRecyclerViewAdapter(context, schedules!!, viewModel.readOnly) { schedule ->
checkEnableSave(context, schedule)
}
}
-
- // Sync
- if (viewModel.isStreamer && !viewModel.readOnly && !viewModel.firstSync(context)) {
- PreferencesProvider.set(context, PreferencesKey.FIRST_SYNC, true)
- syncSchedule(context)
- }
}
-
- setupBody(schedules)
- setupButtons()
}
private fun setupHeader() {
@@ -165,20 +153,48 @@ class UserFragment : Fragment() {
transaction?.commit()
}
- private fun setupBody(schedule: List?) {
+ private fun setupContent() {
+
+ setupHeader()
+ setupInfo()
+ setupBody()
+ setupButtons()
+ }
+
+ private fun setupInfo() {
+
+ val transaction = activity?.supportFragmentManager?.beginTransaction()
+
+ if (!viewModel.isStreamer) {
+ infoFragment = InfoRouter().fragment(InfoViewType.STREAMER)
+ } else if (viewModel.onHolidays) {
+ infoFragment = if (viewModel.readOnly) InfoRouter().fragment(InfoViewType.USER_HOLIDAYS) else InfoRouter().fragment(InfoViewType.HOLIDAY)
+ } else if (schedules.isNullOrEmpty()) {
+ infoFragment = InfoRouter().fragment(InfoViewType.SCHEDULE)
+ }
+
+ infoFragment?.let {
+ transaction?.replace(R.id.frameLayoutInfo, it)
+ }
+ transaction?.disallowAddToBackStack()
+ transaction?.commit()
+ }
+
+ private fun setupBody() {
binding.imageButtonSync.visibility = View.GONE
+ binding.textViewSchedule.visibility = View.VISIBLE
if (viewModel.isStreamer) {
binding.switchStreamer.isChecked = true
- if (viewModel.readOnly && schedule.isNullOrEmpty()) {
+ if (viewModel.onHolidays || (viewModel.readOnly && schedules.isNullOrEmpty())) {
infoFragment?.view?.visibility = View.VISIBLE
binding.recyclerViewSchedule.visibility = View.GONE
} else {
infoFragment?.view?.visibility = View.GONE
binding.recyclerViewSchedule.visibility = View.VISIBLE
}
- if (!viewModel.readOnly) {
+ if (!viewModel.onHolidays && !viewModel.readOnly) {
binding.imageButtonSync.visibility = View.VISIBLE
}
} else {
@@ -191,13 +207,17 @@ class UserFragment : Fragment() {
binding.textViewStreamer.visibility = View.GONE
binding.switchStreamer.visibility = View.GONE
}
+
+ if (viewModel.onHolidays || !viewModel.isStreamer) {
+ binding.textViewSchedule.visibility = View.INVISIBLE
+ }
}
private fun setupButtons() {
binding.buttonSaveSchedule.visibility = if (viewModel.isStreamer) View.VISIBLE else View.GONE
- if (viewModel.readOnly) {
+ if (viewModel.onHolidays || viewModel.readOnly) {
binding.layoutButtons.visibility = View.GONE
}
}
@@ -229,4 +249,13 @@ class UserFragment : Fragment() {
binding.buttonSaveSchedule.enable(viewModel.checkEnableSave(context, schedule))
}
+ private fun checkShowScheduleAlert(context: Context) {
+
+ // Sync
+ if (viewModel.isStreamer && !viewModel.readOnly && !viewModel.firstSync(context)) {
+ PreferencesProvider.set(context, PreferencesKey.FIRST_SYNC, true)
+ syncSchedule(context)
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserViewModel.kt b/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserViewModel.kt
index 0de269d..38e761a 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserViewModel.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/account/user/UserViewModel.kt
@@ -18,6 +18,7 @@ class UserViewModel : ViewModel() {
private var user: User? = null // Read only user
val readOnly get() = user != null
val isStreamer get() = getUser()?.streamer ?: false
+ val onHolidays get() = getUser()?.settings?.onHolidays ?: false
// Localization
@@ -27,6 +28,8 @@ class UserViewModel : ViewModel() {
val streamerText = R.string.user_streamer
val syncAlertTitleText = R.string.user_syncschedule_alert_title
val syncAlertBodyText = R.string.user_syncschedule_alert_body
+ val syncInfoAlertTitleText = R.string.user_scheduleinfo_alert_title
+ val syncInfoAlertBodyText = R.string.user_scheduleinfo_alert_body
val okText = R.string.accept
val cancelText = R.string.cancel
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/common/rows/SearchQueryRecyclerViewAdapter.kt b/app/src/main/java/com/mouredev/twitimer/usecases/common/rows/SearchQueryRecyclerViewAdapter.kt
index 4212723..8e79f26 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/common/rows/SearchQueryRecyclerViewAdapter.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/common/rows/SearchQueryRecyclerViewAdapter.kt
@@ -88,6 +88,7 @@ class SearchQueryRecyclerViewAdapter(val context: Context, var users: List, val
// User
binding.textViewUser.text = user.displayName ?: ""
+ binding.imageViewHoliday.visibility = if (user.settings?.onHolidays == true) { View.VISIBLE } else { View.GONE }
// Avatar
UIUtil.loadAvatar(context, user.profileImageUrl, user.login, binding.imageViewAvatar)
@@ -119,7 +120,7 @@ class SearchRecyclerViewAdapter(val context: Context, var users: List, val
val binding = viewHolder.binding
binding.textViewUser.font(FontSize.BUTTON, FontType.BOLD, ContextCompat.getColor(context, R.color.light))
- binding.textViewUser.maxLines = 1
+ binding.textViewUser.maxLines = 2
binding.buttonChannel.background = ContextCompat.getDrawable(context, R.drawable.channel_button_round_dark)
binding.textViewChannelTitle.font(FontSize.CAPTION, FontType.LIGHT, ContextCompat.getColor(context, R.color.light))
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoFragment.kt b/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoFragment.kt
index 206d22d..95c542b 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoFragment.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoFragment.kt
@@ -151,6 +151,17 @@ class InfoFragment : Fragment() {
}
InfoViewType.AUTH ->
binding.linearLayoutFooter.visibility = View.GONE
+
+ InfoViewType.HOLIDAY, InfoViewType.USER_HOLIDAYS -> {
+ binding.buttonAction.visibility = View.GONE
+ binding.textViewFooterFirst.text = getString(viewModel.advice(1))
+ context?.let { context ->
+ viewModel.icon(1)?.let { icon ->
+ binding.imageViewFooterFirst.setImageDrawable(ContextCompat.getDrawable(context, icon))
+ }
+ binding.linearLayoutFooterSecond.visibility = View.GONE
+ }
+ }
}
}
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoViewModel.kt b/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoViewModel.kt
index 2dae7d6..60ce075 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoViewModel.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/common/views/info/InfoViewModel.kt
@@ -12,7 +12,7 @@ import java.util.*
enum class InfoViewType {
- COUNTDOWN, SEARCH, CHANNEL, STREAMER, AUTH, SCHEDULE
+ COUNTDOWN, SEARCH, CHANNEL, STREAMER, AUTH, SCHEDULE, HOLIDAY, USER_HOLIDAYS
}
class InfoViewModel : ViewModel() {
@@ -33,6 +33,7 @@ class InfoViewModel : ViewModel() {
InfoViewType.STREAMER -> R.drawable.radio_microphone
InfoViewType.AUTH -> R.drawable.secure_connection
InfoViewType.SCHEDULE -> R.drawable.schedule
+ InfoViewType.HOLIDAY, InfoViewType.USER_HOLIDAYS -> R.drawable.vacation
}
}
@@ -45,6 +46,7 @@ class InfoViewModel : ViewModel() {
InfoViewType.STREAMER -> R.string.info_streamer_title
InfoViewType.AUTH -> R.string.info_auth_title
InfoViewType.SCHEDULE -> R.string.info_schedule_title
+ InfoViewType.HOLIDAY, InfoViewType.USER_HOLIDAYS -> R.string.info_holiday_title
}
}
@@ -57,6 +59,8 @@ class InfoViewModel : ViewModel() {
InfoViewType.STREAMER -> R.string.info_streamer_body
InfoViewType.AUTH -> R.string.info_auth_body
InfoViewType.SCHEDULE -> R.string.info_schedule_body
+ InfoViewType.HOLIDAY -> R.string.info_holiday_body
+ InfoViewType.USER_HOLIDAYS -> R.string.info_userholidays_body
}
}
@@ -68,15 +72,18 @@ class InfoViewModel : ViewModel() {
InfoViewType.STREAMER -> R.string.info_streamer_advice_1
InfoViewType.AUTH -> R.string.info_auth_advice_1
InfoViewType.SCHEDULE -> R.string.info_schedule_advice_1
+ InfoViewType.HOLIDAY -> R.string.info_holiday_advice_1
+ InfoViewType.USER_HOLIDAYS -> R.string.info_userholidays_advice_1
}
}
fun icon(number: Int): Int? {
return when (type) {
InfoViewType.SEARCH -> if (number == 1) R.drawable.calendar_add else R.drawable.calendar_remove
- InfoViewType.CHANNEL -> R.drawable.megaphone
+ InfoViewType.CHANNEL, InfoViewType.USER_HOLIDAYS -> R.drawable.megaphone
InfoViewType.STREAMER -> R.drawable.calendar
InfoViewType.SCHEDULE -> R.drawable.time_clock_circle
+ InfoViewType.HOLIDAY -> R.drawable.settings
InfoViewType.COUNTDOWN, InfoViewType.AUTH -> null
}
}
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/countdown/CountdownFragment.kt b/app/src/main/java/com/mouredev/twitimer/usecases/countdown/CountdownFragment.kt
index 2ec9485..37802e9 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/countdown/CountdownFragment.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/countdown/CountdownFragment.kt
@@ -33,6 +33,7 @@ class CountdownFragment : Fragment(), InfoFragmentListener {
private lateinit var viewModel: CountdownViewModel
private var listener: CountdownFragmentListener? = null
private var loaded = false
+ private var resumed = false
// Initialization
@@ -63,6 +64,16 @@ class CountdownFragment : Fragment(), InfoFragmentListener {
load()
}
+ override fun onResume() {
+ super.onResume()
+ if (resumed) {
+ context?.let { context ->
+ viewModel.load(context)
+ }
+ }
+ resumed = true
+ }
+
// Public
fun load() {
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/menu/MenuActivity.kt b/app/src/main/java/com/mouredev/twitimer/usecases/menu/MenuActivity.kt
index a9344a3..06971fc 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/menu/MenuActivity.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/menu/MenuActivity.kt
@@ -1,11 +1,7 @@
package com.mouredev.twitimer.usecases.menu
-import android.graphics.Bitmap
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import com.mouredev.twitimer.R
import com.mouredev.twitimer.databinding.ActivityMenuBinding
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsActivity.kt b/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsActivity.kt
index 2672115..22480c2 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsActivity.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsActivity.kt
@@ -60,6 +60,8 @@ class SettingsActivity : AppCompatActivity() {
private fun localize() {
+ binding.textViewHolidayMode.text = getText(viewModel.holidayTitleText)
+ binding.textViewHolidayModeDetail.text = getText(viewModel.holidayBodyText)
binding.textViewSocialMedia.text = getText(viewModel.socialMediaText)
binding.editTextDiscord.hint = getText(viewModel.discordPlaceholder)
binding.editTextYouTube.hint = getText(viewModel.youtubePlaceholder)
@@ -68,6 +70,7 @@ class SettingsActivity : AppCompatActivity() {
binding.editTextTikTok.hint = getText(viewModel.tiktokPlaceholder)
binding.buttonCloseSession.text = getText(viewModel.closeText)
binding.buttonSaveSettings.text = getText(viewModel.saveText)
+ binding.buttonDeleteAccount.text = getText(viewModel.deleteButtonText)
}
private fun setup() {
@@ -77,6 +80,9 @@ class SettingsActivity : AppCompatActivity() {
// Header
addClose()
+ // Holiday mode
+ setupHolidayMode()
+
// Social media
setupSocialMedia()
@@ -85,8 +91,24 @@ class SettingsActivity : AppCompatActivity() {
}
+ private fun setupHolidayMode() {
+
+ // Holiday mode
+
+ binding.textViewHolidayMode.font(FontSize.HEAD, color = ContextCompat.getColor(this, R.color.text))
+ binding.textViewHolidayModeDetail.font(FontSize.SUBHEAD, color = ContextCompat.getColor(this, R.color.text))
+
+ binding.switchHolidayMode.isChecked = viewModel.settings.onHolidays == true
+ binding.switchHolidayMode.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.settings.onHolidays = isChecked
+ checkEnableSave()
+ }
+ }
+
private fun setupSocialMedia() {
+ // Social media
+
binding.textViewSocialMedia.font(FontSize.HEAD, color = ContextCompat.getColor(this, R.color.text))
// Discord
@@ -254,6 +276,17 @@ class SettingsActivity : AppCompatActivity() {
}
})
+ // Delete account
+
+ binding.textViewDeleteAccount.font(FontSize.HEAD, color = ContextCompat.getColor(this, R.color.text))
+
+ binding.buttonDeleteAccount.destroy {
+ UIUtil.showAlert(this, getString(viewModel.deleteButtonText), getString(viewModel.deleteAlertText), getString(viewModel.deleteTitleText), {
+ hideSoftInput()
+ viewModel.delete(this)
+ currentEditText?.clearFocus()
+ }, getString(viewModel.cancelText), true)
+ }
}
private fun setupFooter() {
@@ -270,8 +303,13 @@ class SettingsActivity : AppCompatActivity() {
binding.buttonSaveSettings.enable(false)
binding.buttonSaveSettings.primary {
hideSoftInput()
- viewModel.save(this)
- binding.buttonSaveSettings.enable(false)
+ if (viewModel.saveHolidays(this)) {
+ UIUtil.showAlert(this, getString(viewModel.holidayTitleText), getString(viewModel.holidayAlertText), getString(viewModel.okText), {
+ save()
+ }, getString(viewModel.cancelText), true)
+ } else {
+ save()
+ }
currentEditText?.clearFocus()
}
}
@@ -301,8 +339,12 @@ class SettingsActivity : AppCompatActivity() {
}
private fun checkEnableSave() {
- binding.buttonSaveSettings.enable(viewModel.checkEnableSave(this))
+ binding.buttonSaveSettings.enable(viewModel.enableSave(this))
}
+ private fun save() {
+ viewModel.save(this)
+ binding.buttonSaveSettings.enable(false)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsViewModel.kt b/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsViewModel.kt
index 08df413..e4f2496 100644
--- a/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/mouredev/twitimer/usecases/settings/SettingsViewModel.kt
@@ -19,6 +19,9 @@ class SettingsViewModel : ViewModel() {
// Localization
+ val holidayTitleText = R.string.settings_holiday_title
+ val holidayBodyText = R.string.settings_holiday_body
+ val holidayAlertText = R.string.settings_holiday_alert
val socialMediaText = R.string.settings_socialmedia
val discordPlaceholder = R.string.settings_discord_placeholder
val youtubePlaceholder = R.string.settings_youtube_placeholder
@@ -28,34 +31,42 @@ class SettingsViewModel : ViewModel() {
val closeText = R.string.user_closesession
val closeAlertText = R.string.user_closesession_alert_body
val saveText = R.string.settings_savesettings
+ val deleteTitleText = R.string.settings_deleteaccount_title
+ val deleteButtonText = R.string.settings_deleteaccount_button
+ val deleteAlertText = R.string.settings_deleteaccount_alert
val okText = R.string.accept
val cancelText = R.string.cancel
// Public
- fun checkEnableSave(context: Context): Boolean {
-
- val savedSettings = Session.instance.savedSettings(context)
+ fun close(context: SettingsActivity) {
- if (savedSettings != settings) {
- return true
+ Session.instance.revoke(context) {
+ context.onBackPressed()
}
- return false
}
fun save(context: Context) {
Session.instance.save(context, settings)
}
- fun close(context: SettingsActivity) {
+ fun enableSave(context: Context): Boolean {
+ return Session.instance.savedSettings(context) != settings
+ }
- Session.instance.revoke(context) {
- context.onBackPressed()
- }
+ fun saveHolidays(context: Context): Boolean {
+ return settings.onHolidays == true && (settings.onHolidays != Session.instance.savedSettings(context)?.onHolidays)
}
fun restoreSaveSettings(context: Context) {
Session.instance.user?.settings = Session.instance.savedSettings(context)
}
+ fun delete(context: SettingsActivity) {
+
+ Session.instance.delete(context) {
+ context.onBackPressed()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/mouredev/twitimer/util/UIUtil.kt b/app/src/main/java/com/mouredev/twitimer/util/UIUtil.kt
index 8cd5cc9..6245365 100644
--- a/app/src/main/java/com/mouredev/twitimer/util/UIUtil.kt
+++ b/app/src/main/java/com/mouredev/twitimer/util/UIUtil.kt
@@ -24,7 +24,7 @@ object UIUtil {
// Alert
- fun showAlert(context: Context, title: String, message: String, positive: String, positiveAction: (() -> Unit)? = null, negative: String? = null) {
+ fun showAlert(context: Context, title: String, message: String, positive: String, positiveAction: (() -> Unit)? = null, negative: String? = null, destroy: Boolean = false) {
val builder = AlertDialog.Builder(context, R.style.CustomDialogTheme)
builder.setTitle(title)
@@ -42,7 +42,7 @@ object UIUtil {
val dialog: AlertDialog = builder.create()
dialog.show()
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(context, R.color.light))
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(context, if (destroy) R.color.live else R.color.light))
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ContextCompat.getColor(context, R.color.dark))
}
diff --git a/app/src/main/java/com/mouredev/twitimer/util/extension/ButtonExtension.kt b/app/src/main/java/com/mouredev/twitimer/util/extension/ButtonExtension.kt
index e9939f0..46395aa 100644
--- a/app/src/main/java/com/mouredev/twitimer/util/extension/ButtonExtension.kt
+++ b/app/src/main/java/com/mouredev/twitimer/util/extension/ButtonExtension.kt
@@ -51,6 +51,17 @@ fun AppCompatButton.secondary(listener: View.OnClickListener) {
setOnClickListener(listener)
}
+fun AppCompatButton.destroy(listener: View.OnClickListener) {
+
+ maxLines = 1
+ ellipsize = TextUtils.TruncateAt.END
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, FontSize.BUTTON.size.toFloat())
+ setTextColor(ContextCompat.getColor(context, R.color.light))
+ setTypeface(Typeface.createFromAsset(context.assets, FontType.BOLD.path), Typeface.NORMAL)
+ background = ContextCompat.getDrawable(context, R.drawable.destroy_button_round)
+ setOnClickListener(listener)
+}
+
fun Button.picker(color: Int = ContextCompat.getColor(context, R.color.dark)) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, FontSize.SUBHEAD.size.toFloat())
diff --git a/app/src/main/res/drawable-hdpi/holiday.png b/app/src/main/res/drawable-hdpi/holiday.png
new file mode 100644
index 0000000..24dd120
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/holiday.png differ
diff --git a/app/src/main/res/drawable-mdpi/vacation.xml b/app/src/main/res/drawable-mdpi/vacation.xml
new file mode 100644
index 0000000..8a3ba5b
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/vacation.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-xhdpi/holiday.png b/app/src/main/res/drawable-xhdpi/holiday.png
new file mode 100644
index 0000000..693cc42
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/holiday.png differ
diff --git a/app/src/main/res/drawable/destroy_button_round.xml b/app/src/main/res/drawable/destroy_button_round.xml
new file mode 100644
index 0000000..0372625
--- /dev/null
+++ b/app/src/main/res/drawable/destroy_button_round.xml
@@ -0,0 +1,18 @@
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/holiday.png b/app/src/main/res/drawable/holiday.png
new file mode 100644
index 0000000..c5e1e16
Binary files /dev/null and b/app/src/main/res/drawable/holiday.png differ
diff --git a/app/src/main/res/drawable/schedule.xml b/app/src/main/res/drawable/schedule.xml
index d025dce..35fe642 100644
--- a/app/src/main/res/drawable/schedule.xml
+++ b/app/src/main/res/drawable/schedule.xml
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
@@ -186,6 +224,27 @@
+
+
+
+
diff --git a/app/src/main/res/layout/info_fragment.xml b/app/src/main/res/layout/info_fragment.xml
index f9727fb..d8ee9de 100644
--- a/app/src/main/res/layout/info_fragment.xml
+++ b/app/src/main/res/layout/info_fragment.xml
@@ -106,6 +106,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
+ android:gravity="center_vertical"
android:orientation="horizontal">
+
+
+
+
+ app:layout_constraintTop_toTopOf="@+id/textViewStreamer" />
Soy streamer
Sincronizar horario de Twitch
¿Quieres sincronizar tu calendario recurrente de Twitch? Se sobrescribirá la programación de Twitimer.
+ Sincronización de horarios
+ Los horarios guardados desde Twitimer no sobrescribirán los de Twitch para evitar interferir en la plataforma y permitirte gestionarlos de forma más intuitiva y ágil.
@@ -88,6 +90,13 @@
El streamer no tiene horarios disponibles. Puede que aún no los haya añadido desde Twitimer (o quizá se está tomando unas vacaciones).
Prueba a revisarlos más tarde. Cuando tenga algún horario aparecerá una cuenta atrás en el listado de próximas emisiones.
+ Modo vacaciones activado
+ El modo vacaciones oculta tus horarios de transmisión a tus seguidores y notifica a tu audiencia.
+ Desactiva el modo vacaciones desde los ajustes de tu usuario.
+
+ Este usuario se encuentra de vacaciones. Los horarios se mostrarán en el momento que el streamer regrese.
+ Si tienes las notificaciones activas, recibirás un aviso.
+
por
¡Hola! Mi nombre es Brais Moure, soy el creador de Twitimer.\n\nEsta App se ha desarrollado para ayudar a usuarios de Twitch, pero sobre todo pensando en generar contenido formativo para toda la comunidad de programadores y programadoras interesada en el mundo del desarrollo de apps para dispositivos móviles. Ellos han hecho posible el proyecto (ante todo quiero que sea una App gratuita y en constante evolución).\n\nSi te apetece unirte a nuestra comunidad, dejarme algún tipo de sugerencia para mejorar, o apoyar la continuidad del proyecto (unos bits nunca vienen mal para pagar el servidor…), puedes encontrarme en Twitch y resto de redes sociales como @mouredev.\n\n¡Muchas gracias!
@@ -97,6 +106,9 @@
+ Modo vacaciones
+ El modo vacaciones oculta tus horarios de transmisión a tus seguidores y notifica a tu audiencia.
+ ¿Quieres activar el modo vacaciones? Se notificará a tus seguidores.
Redes sociales
Código de invitación de Discord
Usuario de YouTube
@@ -104,5 +116,8 @@
Usuario de Instagram
Usuario de TikTok
Guardar ajustes
+ Eliminar cuenta
+ Eliminar mi cuenta de Twitimer
+ Se eliminará tu cuenta de Twitimer y todos sus datos asociados. Tu cuenta de Twitch no se verá afectada.
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b3985c9..f6f446b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,6 +59,8 @@
I\'m a streamer
Synchronize Twitch schedule
Do you want to sync your recurring Twitch calendar? The Twitimer schedule will be overwritten.
+ Schedules synchronization
+ The schedules saved from Twitimer will not overwrite those of Twitch to avoid interfering with the platform and allow you to manage them in a more intuitive and agile way.
@@ -88,6 +90,13 @@
The streamer does not have available schedules. He may not have added them Twitimer yet (or he may be on holidays).
Try checking them out later. When the streamer has a schedule, a countdown will appear in the list of upcoming broadcasts.
+ Holiday mode activated
+ Holiday mode hides your broadcast schedules from your followers and notifies your audience.
+ Deactivate holiday mode from your user settings.
+
+ This user is on holidays. Schedules will be displayed at the moment that the streamer returns.
+ If you have active notifications, you will receive a notification.
+
by
@@ -99,6 +108,9 @@
+ Holiday mode
+ Holiday mode hides your broadcast schedules from your followers and notifies your audience.
+ Do you want to activate the holiday mode? Your followers will be notified.
Social media
Discord invitation link code
YouTube user
@@ -106,5 +118,8 @@
Instagram user
TikTok user
Save settings
+ Delete account
+ Delete my Twitimer account
+ Your Twitimer account and all their associated data will be deleted. Your Twitch account will not be affected.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b36efd2..b3fbc5e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'