Skip to content

Commit a4012c6

Browse files
authored
Merge pull request #7 from elimu-ai/feat/implement_video_views
Init codebase: First version of Filamu
2 parents 3d90c0a + 5cd64f3 commit a4012c6

File tree

75 files changed

+1925
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1925
-0
lines changed

.github/workflows/gradle-build.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Gradle Build
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build:
11+
strategy:
12+
matrix:
13+
os: [ubuntu-latest, macos-latest, windows-latest]
14+
java: [17, 21]
15+
runs-on: ${{ matrix.os }}
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-java@v4
19+
with:
20+
distribution: temurin
21+
java-version: ${{ matrix.java }}
22+
- uses: gradle/gradle-build-action@v3
23+
24+
- name: Grant execute permission to Gradle wrapper
25+
run: chmod +x ./gradlew
26+
27+
- run: ./gradlew clean build

.gitignore

+17
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,20 @@
2222
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
2323
hs_err_pid*
2424
replay_pid*
25+
26+
# Local configuration file (sdk path, etc)
27+
local.properties
28+
29+
# Gradle files
30+
.gradle/
31+
build/
32+
data-video/build/
33+
34+
.DS_Store
35+
36+
# IntelliJ
37+
*.iml
38+
.idea
39+
40+
# Kotlin
41+
.kotlin/

app/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'org.jetbrains.kotlin.android'
3+
apply plugin: 'dagger.hilt.android.plugin'
4+
apply plugin: 'kotlin-kapt'
5+
6+
7+
android {
8+
compileSdk 35
9+
namespace "ai.elimu.filamu"
10+
11+
defaultConfig {
12+
minSdkVersion 26
13+
targetSdkVersion 35
14+
versionCode 1000000
15+
versionName '1.0.0'
16+
setProperty("archivesBaseName", "${applicationId}-${versionCode}")
17+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18+
}
19+
20+
buildTypes {
21+
debug {
22+
applicationIdSuffix ".debug"
23+
versionNameSuffix "-debug"
24+
manifestPlaceholders = [contentProviderApplicationId: "ai.elimu.content_provider.debug"]
25+
buildConfigField("String", "CONTENT_PROVIDER_APPLICATION_ID", '"ai.elimu.content_provider.debug"')
26+
buildConfigField("String", "ANALYTICS_APPLICATION_ID", '"ai.elimu.analytics.debug"')
27+
}
28+
release {
29+
minifyEnabled false
30+
manifestPlaceholders = [contentProviderApplicationId: "ai.elimu.content_provider"]
31+
buildConfigField("String", "CONTENT_PROVIDER_APPLICATION_ID", '"ai.elimu.content_provider"')
32+
buildConfigField("String", "ANALYTICS_APPLICATION_ID", '"ai.elimu.analytics"')
33+
signingConfig signingConfigs.debug
34+
}
35+
}
36+
37+
compileOptions {
38+
sourceCompatibility JavaVersion.VERSION_17
39+
targetCompatibility JavaVersion.VERSION_17
40+
}
41+
42+
buildFeatures {
43+
buildConfig = true
44+
viewBinding = true
45+
}
46+
47+
kotlinOptions {
48+
jvmTarget = '17'
49+
}
50+
}
51+
52+
dependencies {
53+
implementation fileTree(dir: 'libs', include: ['*.jar'])
54+
55+
implementation project(':data-video')
56+
implementation libs.elimu.model // See https://jitpack.io/#elimu-ai/model
57+
implementation libs.elimu.content.provider // See https://jitpack.io/#elimu-ai/content-provider
58+
implementation libs.elimu.analytics // See https://jitpack.io/#elimu-ai/analytics
59+
60+
implementation libs.androidx.appcompat
61+
implementation libs.material
62+
implementation libs.androidx.constraint.layout
63+
64+
implementation libs.androidx.media3.exoplayer
65+
implementation libs.androidx.media3.ui
66+
67+
implementation libs.glide
68+
implementation libs.timber
69+
70+
implementation libs.coroutines.android
71+
implementation libs.coroutines.core
72+
implementation libs.androidx.core.ktx
73+
implementation libs.hilt.android
74+
kapt libs.hilt.compiler
75+
76+
testImplementation libs.junit
77+
78+
androidTestImplementation libs.androidx.junit
79+
androidTestImplementation libs.androidx.espresso
80+
}
81+
82+
task ensureCleanRepo {
83+
doLast {
84+
if (!grgit.repository.jgit.status().call().clean) {
85+
throw new GradleException('Git status is not clean, please stash your changes!')
86+
}
87+
}
88+
}
89+
task releaseClean(dependsOn: ensureCleanRepo) {
90+
doLast {
91+
def clean = true
92+
def applicationId = android.defaultConfig.applicationId
93+
94+
String headCommitMessage = grgit.head().shortMessage
95+
while (headCommitMessage.contains("[gradle-release-task]")) {
96+
clean = false
97+
println "Found git commit: $headCommitMessage"
98+
if (headCommitMessage.indexOf("$applicationId-") > -1) {
99+
def tagName = headCommitMessage.split("$applicationId-")[1]
100+
println "Removing the git tag: $tagName"
101+
try {
102+
grgit.tag.remove {
103+
names = [tagName]
104+
}
105+
} catch (Exception e) {
106+
println "Error while removing git tag:\n $e"
107+
}
108+
}
109+
println "Resetting the git commit permanently!"
110+
grgit.reset(commit: "HEAD~1", mode: "hard")
111+
headCommitMessage = grgit.head().shortMessage
112+
113+
}
114+
if (clean){
115+
println "Repository is already clean"
116+
}
117+
println "Done!"
118+
}
119+
}
120+
121+
// Task parameters:
122+
// bumpVersion -> if available will specify new versionName directly and ignores the `bumpType` parameter.
123+
// bumpType[major|minor|patch] -> will specify how the version bumping occurs.
124+
task releasePrepare(dependsOn: ensureCleanRepo) {
125+
doLast {
126+
def applicationId = android.defaultConfig.applicationId
127+
def versionName = android.defaultConfig.versionName
128+
129+
if (versionName.indexOf("-") > -1) {
130+
versionName = versionName.split("-")[0]
131+
}
132+
133+
// Prepare the release commit with the specific tag.
134+
String buildText = buildFile.getText()
135+
buildText = buildText.replaceFirst(/versionName(\s+.*)/, "versionName '$versionName'")
136+
buildFile.setText(buildText) //replace the build file's text
137+
grgit.add(patterns: ['app/build.gradle'])
138+
grgit.commit(message: "[gradle-release-task] prepare release $applicationId-$versionName")
139+
try {
140+
grgit.tag.add {
141+
name = versionName
142+
message = "Release of $versionName"
143+
}
144+
} catch (Exception e) {
145+
throw new GradleException("Failed to tag the repo, error:\n $e")
146+
}
147+
148+
149+
// Set new version name from input parameters.
150+
def newVersionName
151+
if (project.properties.containsKey("bumpVersion")) {
152+
newVersionName = project.properties["bumpVersion"]
153+
println "Bumping the version directly (bumpVersion=$newVersionName)"
154+
} else if (project.properties.containsKey("bumpType")) {
155+
def (major, minor, patch) = versionName.tokenize('.')
156+
switch (bumpType) {
157+
case "major":
158+
major = major.toInteger() + 1
159+
minor = 0
160+
patch = 0
161+
break
162+
case "minor":
163+
minor = minor.toInteger() + 1
164+
break
165+
case "patch":
166+
patch = patch.toInteger() + 1
167+
break
168+
}
169+
newVersionName = "$major.$minor.$patch"
170+
} else {
171+
throw new GradleException('Either bumpType or bumpVersion parameters should be provided')
172+
}
173+
174+
// Prepare for next development iteration.
175+
def versionCode = android.defaultConfig.versionCode
176+
def newVersionCode = versionCode + 1
177+
println "Bumping versionName from $versionName to $newVersionName"
178+
println "Bumping versionCode from $versionCode to $newVersionCode"
179+
buildText = buildFile.getText()
180+
buildText = buildText.replaceFirst(/versionName(\s+.*)/, "versionName '$newVersionName-SNAPSHOT'")
181+
buildText = buildText.replaceFirst(/versionCode(\s+.*)/, "versionCode $newVersionCode")
182+
buildFile.setText(buildText) //replace the build file's text
183+
grgit.add(patterns: ['app/build.gradle'])
184+
grgit.commit(message: "[gradle-release-task] prepare for next development iteration")
185+
println "Done!"
186+
}
187+
188+
}
189+
190+
task releasePerform(dependsOn: ensureCleanRepo) {
191+
doLast {
192+
boolean force = false
193+
if (project.properties.containsKey("force")) {
194+
force = project.properties["force"]
195+
}
196+
println "Pushing the newest commits to the remote repository (force: $force)"
197+
try {
198+
grgit.push(force: force, tags: true)
199+
} catch (Exception e) {
200+
throw new GradleException("Failed to push to the repo,\n" +
201+
" you can try using -Pforce=true parameter to force the push, error: \n$e")
202+
}
203+
println "Done!"
204+
}
205+
}

app/src/main/AndroidManifest.xml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="ai.elimu.filamu">
4+
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
7+
<application
8+
android:name=".BaseApplication"
9+
android:allowBackup="true"
10+
android:icon="@mipmap/ic_launcher"
11+
android:label="@string/app_name"
12+
android:networkSecurityConfig="@xml/network_security_config"
13+
android:roundIcon="@mipmap/ic_launcher_round"
14+
android:supportsRtl="true"
15+
android:theme="@style/AppTheme">
16+
17+
<activity
18+
android:name=".MainActivity"
19+
android:theme="@style/AppTheme"
20+
android:exported="true">
21+
<intent-filter>
22+
<action android:name="android.intent.action.MAIN" />
23+
24+
<category android:name="android.intent.category.LAUNCHER" />
25+
</intent-filter>
26+
</activity>
27+
28+
<activity
29+
android:name=".ui.VideosActivity"
30+
android:exported="false"/>
31+
32+
<activity
33+
android:name=".ui.video.VideoActivity"
34+
android:theme="@style/AppTheme.NoTitle.Fullscreen"
35+
android:exported="false"/>
36+
</application>
37+
</manifest>
335 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package ai.elimu.filamu
2+
3+
import android.app.Application
4+
import android.content.pm.ApplicationInfo
5+
import dagger.hilt.android.HiltAndroidApp
6+
import timber.log.Timber
7+
8+
@HiltAndroidApp
9+
class BaseApplication : Application() {
10+
11+
override fun onCreate() {
12+
super.onCreate()
13+
val isDebuggable = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
14+
if (isDebuggable) {
15+
Timber.plant(Timber.DebugTree())
16+
}
17+
Timber.tag(javaClass.name).i("onCreate")
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ai.elimu.filamu
2+
3+
import ai.elimu.filamu.ui.VideosActivity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import androidx.appcompat.app.AppCompatActivity
7+
import timber.log.Timber
8+
9+
class MainActivity : AppCompatActivity() {
10+
private val TAG = javaClass.name
11+
12+
override fun onCreate(savedInstanceState: Bundle?) {
13+
Timber.tag(TAG).i("onCreate")
14+
super.onCreate(savedInstanceState)
15+
16+
setContentView(R.layout.activity_main)
17+
18+
// Verify that the content-provider APK has been installed
19+
// TODO
20+
}
21+
22+
override fun onStart() {
23+
Timber.tag(TAG).i("onStart")
24+
super.onStart()
25+
26+
val intent = Intent(this, VideosActivity::class.java)
27+
startActivity(intent)
28+
29+
finish()
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package ai.elimu.filamu.data
2+
3+
import android.net.Uri
4+
import androidx.media3.common.util.UnstableApi
5+
import androidx.media3.datasource.DataSource
6+
import androidx.media3.datasource.DataSpec
7+
import androidx.media3.datasource.TransferListener
8+
import java.io.ByteArrayInputStream
9+
import java.io.InputStream
10+
11+
@UnstableApi
12+
class ByteArrayDataSource(private val byteArray: ByteArray) : DataSource {
13+
private var inputStream: InputStream? = null
14+
private var readBytes = 0L
15+
16+
override fun open(dataSpec: DataSpec): Long {
17+
inputStream = ByteArrayInputStream(byteArray)
18+
readBytes = 0L
19+
return byteArray.size.toLong()
20+
}
21+
22+
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
23+
val bytesRead = inputStream?.read(buffer, offset, length) ?: -1
24+
if (bytesRead > 0) {
25+
readBytes += bytesRead
26+
}
27+
return bytesRead
28+
}
29+
30+
override fun addTransferListener(transferListener: TransferListener) {
31+
}
32+
33+
override fun getUri(): Uri? = null
34+
35+
override fun close() {
36+
inputStream?.close()
37+
inputStream = null
38+
}
39+
}

0 commit comments

Comments
 (0)